001 /**
002 * Copyright 2005-2012 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.kuali.rice.kim.impl.identity;
017
018 import org.apache.commons.beanutils.PropertyUtils;
019 import org.apache.commons.lang.StringUtils;
020 import org.apache.log4j.Logger;
021 import org.kuali.rice.core.api.CoreApiServiceLocator;
022 import org.kuali.rice.core.api.criteria.Predicate;
023 import org.kuali.rice.core.api.criteria.PredicateUtils;
024 import org.kuali.rice.core.api.criteria.QueryByCriteria;
025 import org.kuali.rice.kim.api.identity.IdentityService;
026 import org.kuali.rice.kim.api.identity.Person;
027 import org.kuali.rice.kim.api.identity.PersonService;
028 import org.kuali.rice.kim.api.identity.entity.EntityDefault;
029 import org.kuali.rice.kim.api.identity.entity.EntityDefaultQueryResults;
030 import org.kuali.rice.kim.api.identity.external.EntityExternalIdentifierType;
031 import org.kuali.rice.kim.api.identity.principal.Principal;
032 import org.kuali.rice.kim.api.identity.type.EntityTypeContactInfoDefault;
033 import org.kuali.rice.kim.api.role.RoleService;
034 import org.kuali.rice.kim.api.services.KimApiServiceLocator;
035 import org.kuali.rice.kim.impl.KIMPropertyConstants;
036 import org.kuali.rice.kns.service.BusinessObjectMetaDataService;
037 import org.kuali.rice.kns.service.KNSServiceLocator;
038 import org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService;
039 import org.kuali.rice.krad.bo.BusinessObject;
040 import org.kuali.rice.krad.bo.DataObjectRelationship;
041 import org.kuali.rice.krad.lookup.CollectionIncomplete;
042 import org.kuali.rice.krad.util.KRADConstants;
043 import org.kuali.rice.krad.util.KRADPropertyConstants;
044 import org.kuali.rice.krad.util.ObjectUtils;
045
046 import java.security.GeneralSecurityException;
047 import java.util.ArrayList;
048 import java.util.Collection;
049 import java.util.Collections;
050 import java.util.HashMap;
051 import java.util.HashSet;
052 import java.util.Iterator;
053 import java.util.List;
054 import java.util.Map;
055 import java.util.Set;
056
057 /**
058 * This is a description of what this class does - kellerj don't forget to fill this in.
059 *
060 * @author Kuali Rice Team (rice.collab@kuali.org)
061 *
062 */
063 public class PersonServiceImpl implements PersonService {
064
065 private static Logger LOG = Logger.getLogger( PersonServiceImpl.class );
066 protected static final String ENTITY_EXT_ID_PROPERTY_PREFIX = "externalIdentifiers.";
067 protected static final String ENTITY_AFFILIATION_PROPERTY_PREFIX = "affiliations.";
068 protected static final String ENTITY_TYPE_PROPERTY_PREFIX = "entityTypeContactInfos.";
069 protected static final String ENTITY_EMAIL_PROPERTY_PREFIX = "entityTypeContactInfos.emailAddresses.";
070 protected static final String ENTITY_PHONE_PROPERTY_PREFIX = "entityTypeContactInfos.phoneNumbers.";
071 protected static final String ENTITY_ADDRESS_PROPERTY_PREFIX = "entityTypeContactInfos.addresses.";
072 protected static final String ENTITY_NAME_PROPERTY_PREFIX = "names.";
073 protected static final String PRINCIPAL_PROPERTY_PREFIX = "principals.";
074 protected static final String ENTITY_EMPLOYEE_ID_PROPERTY_PREFIX = "employmentInformation.";
075 // KULRICE-4442 Special handling for extension objects
076 protected static final String EXTENSION = "extension";
077
078 private IdentityService identityService;
079 private RoleService roleService;
080 private BusinessObjectMetaDataService businessObjectMetaDataService;
081 private MaintenanceDocumentDictionaryService maintenanceDocumentDictionaryService;
082
083 protected List<String> personEntityTypeCodes = new ArrayList<String>( 4 );
084 // String that can be passed to the lookup framework to create an type = X OR type = Y criteria
085 private String personEntityTypeLookupCriteria = null;
086
087 protected Map<String,String> baseLookupCriteria = new HashMap<String,String>();
088 protected Map<String,String> criteriaConversion = new HashMap<String,String>();
089 protected ArrayList<String> personCachePropertyNames = new ArrayList<String>();
090 {
091 // init the criteria which will need to be applied to every lookup against
092 // the identity data tables
093 baseLookupCriteria.put( KIMPropertyConstants.Person.ACTIVE, "Y" );
094 baseLookupCriteria.put( ENTITY_TYPE_PROPERTY_PREFIX + KRADPropertyConstants.ACTIVE, "Y" );
095
096 // create the field mappings between the Person object and the KimEntity object
097 criteriaConversion.put( KIMPropertyConstants.Person.ENTITY_ID, KIMPropertyConstants.Entity.ID);
098 criteriaConversion.put( KIMPropertyConstants.Person.ACTIVE, PRINCIPAL_PROPERTY_PREFIX + KRADPropertyConstants.ACTIVE );
099 criteriaConversion.put( KIMPropertyConstants.Person.PRINCIPAL_ID, PRINCIPAL_PROPERTY_PREFIX + KIMPropertyConstants.Person.PRINCIPAL_ID );
100 criteriaConversion.put( KIMPropertyConstants.Person.PRINCIPAL_NAME, PRINCIPAL_PROPERTY_PREFIX + KIMPropertyConstants.Person.PRINCIPAL_NAME );
101 criteriaConversion.put( KIMPropertyConstants.Person.FIRST_NAME, "names.firstName" );
102 criteriaConversion.put( KIMPropertyConstants.Person.LAST_NAME, "names.lastName" );
103 criteriaConversion.put( KIMPropertyConstants.Person.MIDDLE_NAME, "names.middleName" );
104 criteriaConversion.put( KIMPropertyConstants.Person.EMAIL_ADDRESS, "entityTypeContactInfos.emailAddresses.emailAddress" );
105 criteriaConversion.put( KIMPropertyConstants.Person.PHONE_NUMBER, "entityTypeContactInfos.phoneNumbers.phoneNumber" );
106 criteriaConversion.put( KIMPropertyConstants.Person.ADDRESS_LINE_1, "entityTypeContactInfos.addresses.line1" );
107 criteriaConversion.put( KIMPropertyConstants.Person.ADDRESS_LINE_2, "entityTypeContactInfos.addresses.line2" );
108 criteriaConversion.put( KIMPropertyConstants.Person.ADDRESS_LINE_3, "entityTypeContactInfos.addresses.line3" );
109 criteriaConversion.put( KIMPropertyConstants.Person.CITY, "entityTypeContactInfos.addresses.city" );
110 criteriaConversion.put( KIMPropertyConstants.Person.STATE_CODE, "entityTypeContactInfos.addresses.stateProvinceCode" );
111 criteriaConversion.put( KIMPropertyConstants.Person.POSTAL_CODE, "entityTypeContactInfos.addresses.postalCode" );
112 criteriaConversion.put( KIMPropertyConstants.Person.COUNTRY_CODE, "entityTypeContactInfos.addresses.countryCode" );
113 criteriaConversion.put( KIMPropertyConstants.Person.CAMPUS_CODE, "affiliations.campusCode" );
114 criteriaConversion.put( KIMPropertyConstants.Person.AFFILIATION_TYPE_CODE, "affiliations.affiliationTypeCode" );
115 criteriaConversion.put( KIMPropertyConstants.Person.EXTERNAL_IDENTIFIER_TYPE_CODE, "externalIdentifiers.externalIdentifierTypeCode" );
116 criteriaConversion.put( KIMPropertyConstants.Person.EXTERNAL_ID, "externalIdentifiers.externalId" );
117 criteriaConversion.put( KIMPropertyConstants.Person.EMPLOYEE_TYPE_CODE, "employmentInformation.employeeTypeCode" );
118 criteriaConversion.put( KIMPropertyConstants.Person.EMPLOYEE_STATUS_CODE, "employmentInformation.employeeStatusCode" );
119 criteriaConversion.put( KIMPropertyConstants.Person.EMPLOYEE_ID, "employmentInformation.employeeId" );
120 criteriaConversion.put( KIMPropertyConstants.Person.BASE_SALARY_AMOUNT, "employmentInformation.baseSalaryAmount" );
121 criteriaConversion.put( KIMPropertyConstants.Person.PRIMARY_DEPARTMENT_CODE, "employmentInformation.primaryDepartmentCode" );
122
123 personCachePropertyNames.add( KIMPropertyConstants.Person.PRINCIPAL_ID );
124 personCachePropertyNames.add( KIMPropertyConstants.Person.PRINCIPAL_NAME );
125 personCachePropertyNames.add( KIMPropertyConstants.Person.ENTITY_ID );
126 personCachePropertyNames.add( KIMPropertyConstants.Person.FIRST_NAME );
127 personCachePropertyNames.add( KIMPropertyConstants.Person.LAST_NAME );
128 personCachePropertyNames.add( KIMPropertyConstants.Person.MIDDLE_NAME );
129 personCachePropertyNames.add( KIMPropertyConstants.Person.CAMPUS_CODE );
130 personCachePropertyNames.add( KIMPropertyConstants.Person.EMPLOYEE_ID );
131 personCachePropertyNames.add( KIMPropertyConstants.Person.PRIMARY_DEPARTMENT_CODE );
132 }
133
134
135 /**
136 * @see org.kuali.rice.kim.api.identity.PersonService#getPerson(java.lang.String)
137 */
138 public Person getPerson(String principalId) {
139 if ( StringUtils.isBlank(principalId) ) {
140 return null;
141 }
142
143 // get the corresponding principal
144 final Principal principal = getIdentityService().getPrincipal( principalId );
145 // get the identity
146 if ( principal != null ) {
147 final EntityDefault entity = getIdentityService().getEntityDefault(principal.getEntityId());
148 // convert the principal and identity to a Person
149 // skip if the person was created from the DB cache
150 if (entity != null ) {
151 return convertEntityToPerson( entity, principal );
152 }
153 }
154 return null;
155 }
156
157 protected PersonImpl convertEntityToPerson( EntityDefault entity, Principal principal ) {
158 try {
159 // get the EntityEntityType for the EntityType corresponding to a Person
160 for ( String entityTypeCode : personEntityTypeCodes ) {
161 EntityTypeContactInfoDefault entType = entity.getEntityType( entityTypeCode );
162 // if no "person" identity type present for the given principal, skip to the next type in the list
163 if ( entType == null ) {
164 continue;
165 }
166 // attach the principal and identity objects
167 // PersonImpl has logic to pull the needed elements from the KimEntity-related classes
168 return new PersonImpl( principal, entity, entityTypeCode );
169 }
170 return null;
171 } catch ( Exception ex ) {
172 // allow runtime exceptions to pass through
173 if ( ex instanceof RuntimeException ) {
174 throw (RuntimeException)ex;
175 }
176 throw new RuntimeException( "Problem building person object", ex );
177 }
178 }
179
180
181 /**
182 * @see org.kuali.rice.kim.api.identity.PersonService#getPersonByPrincipalName(java.lang.String)
183 */
184 public Person getPersonByPrincipalName(String principalName) {
185 if ( StringUtils.isBlank(principalName) ) {
186 return null;
187 }
188
189 // get the corresponding principal
190 final Principal principal = getIdentityService().getPrincipalByPrincipalName( principalName );
191 // get the identity
192 if ( principal != null ) {
193 final EntityDefault entity = getIdentityService().getEntityDefault(principal.getEntityId());
194
195 // convert the principal and identity to a Person
196 if ( entity != null ) {
197 return convertEntityToPerson( entity, principal );
198 }
199 }
200 return null;
201 }
202
203 public Person getPersonByEmployeeId(String employeeId) {
204 if ( StringUtils.isBlank( employeeId ) ) {
205 return null;
206 }
207
208 final List<Person> people = findPeople( Collections.singletonMap(KIMPropertyConstants.Person.EMPLOYEE_ID, employeeId) );
209 if ( !people.isEmpty() ) {
210 return people.get(0);
211
212 }
213
214 // If no person was found above, check for inactive records
215 EntityDefault entity = getIdentityService().getEntityDefaultByEmployeeId(employeeId);
216 if (entity != null) {
217 if ( !entity.getPrincipals().isEmpty() ) {
218 Principal principal = getIdentityService().getPrincipal(entity.getPrincipals().get(0).getPrincipalId());
219 if (principal != null) {
220 return convertEntityToPerson( entity, principal );
221 }
222 }
223 }
224
225 return null;
226 }
227
228 /**
229 * @see org.kuali.rice.kim.api.identity.PersonService#findPeople(Map)
230 */
231 public List<Person> findPeople(Map<String, String> criteria) {
232 return findPeople(criteria, true);
233 }
234
235 /**
236 * @see org.kuali.rice.kim.api.identity.PersonService#findPeople(java.util.Map, boolean)
237 */
238 public List<Person> findPeople(Map<String, String> criteria, boolean unbounded) {
239 List<Person> people = null;
240 // protect from NPEs
241 if ( criteria == null ) {
242 criteria = Collections.emptyMap();
243 }
244 // make a copy so it can be modified safely in this method
245 criteria = new HashMap<String, String>( criteria );
246
247 // extract the role lookup parameters and then remove them since later code will not know what to do with them
248 String roleName = criteria.get( "lookupRoleName" );
249 String namespaceCode = criteria.get( "lookupRoleNamespaceCode" );
250 criteria.remove("lookupRoleName");
251 criteria.remove("lookupRoleNamespaceCode");
252 if ( StringUtils.isNotBlank(namespaceCode) && StringUtils.isNotBlank(roleName) ) {
253 Integer searchResultsLimit = org.kuali.rice.kns.lookup.LookupUtils.getSearchResultsLimit(PersonImpl.class);
254 int searchResultsLimitInt = Integer.MAX_VALUE;
255 if (searchResultsLimit != null) {
256 searchResultsLimitInt = searchResultsLimit.intValue();
257 }
258 if ( LOG.isDebugEnabled() ) {
259 LOG.debug("Performing Person search including role filter: " + namespaceCode + "/" + roleName );
260 }
261 if ( criteria.size() == 1 && criteria.containsKey(KIMPropertyConstants.Person.ACTIVE) ) { // if only active is specified
262 if ( LOG.isDebugEnabled() ) {
263 LOG.debug( "Only active criteria specified, running role search first" );
264 }
265 // in this case, run the role lookup first and pass those results to the person lookup
266 Collection<String> principalIds = getRoleService().getRoleMemberPrincipalIds(namespaceCode, roleName, Collections.<String, String>emptyMap());
267 StringBuffer sb = new StringBuffer(principalIds.size()*15);
268 Iterator<String> pi = principalIds.iterator();
269 while ( pi.hasNext() ) {
270 sb.append( pi.next() );
271 if ( pi.hasNext() ) sb.append( '|' );
272 }
273 // add the list of principal IDs to the lookup so that only matching Person objects are returned
274 criteria.put( KIMPropertyConstants.Person.PRINCIPAL_ID, sb.toString() );
275 people = findPeopleInternal(criteria, false); // can allow internal method to filter here since no more filtering necessary
276 } else if ( !criteria.isEmpty() ) { // i.e., person criteria are specified
277 if ( LOG.isDebugEnabled() ) {
278 LOG.debug( "Person criteria also specified, running that search first" );
279 }
280 // run the person lookup first
281 people = findPeopleInternal(criteria, true); // get all, since may need to be filtered
282 // TODO - now check if these people have the given role
283 // build a principal list
284 List<String> principalIds = peopleToPrincipalIds( people );
285 // get sublist of principals that have the given roles
286 principalIds = getRoleService().getPrincipalIdSubListWithRole(principalIds, namespaceCode, roleName, Collections.<String, String>emptyMap());
287 // re-convert into people objects, wrapping in CollectionIncomplete if needed
288 if ( !unbounded && principalIds.size() > searchResultsLimitInt ) {
289 int actualResultSize = principalIds.size();
290 // trim the list down before converting to people
291 principalIds = new ArrayList<String>(principalIds).subList(0, searchResultsLimitInt); // yes, this is a little wasteful
292 people = getPeople(principalIds); // convert the results to people
293 people = new CollectionIncomplete<Person>( people.subList(0, searchResultsLimitInt), new Long(actualResultSize) );
294 } else {
295 people = getPeople(principalIds);
296 }
297 } else { // only role criteria specified
298 if ( LOG.isDebugEnabled() ) {
299 LOG.debug( "No Person criteria specified - only using role service." );
300 }
301 // run the role criteria to get the principals with the role
302 Collection<String> principalIds = getRoleService().getRoleMemberPrincipalIds(namespaceCode, roleName, Collections.<String, String>emptyMap());
303 if ( !unbounded && principalIds.size() > searchResultsLimitInt ) {
304 int actualResultSize = principalIds.size();
305 // trim the list down before converting to people
306 principalIds = new ArrayList<String>(principalIds).subList(0, searchResultsLimitInt); // yes, this is a little wasteful
307 people = getPeople(principalIds); // convert the results to people
308 people = new CollectionIncomplete<Person>( people.subList(0, searchResultsLimitInt), new Long(actualResultSize) );
309 } else {
310 people = getPeople(principalIds); // convert the results to people
311 }
312 }
313 } else {
314 if ( LOG.isDebugEnabled() ) {
315 LOG.debug( "No Role criteria specified, running person lookup as normal." );
316 }
317 people = findPeopleInternal(criteria, unbounded);
318 }
319
320 // The following change is for KULRICE-5694 - It prevents duplicate rows from being returned for the
321 // person inquiry (In this case, duplicate meaning same entityId, principalId, and principalNm).
322 // This allows for multiple rows to be returned if an entityID has more then one principal name
323 // or more than one principal ID.
324 Set<String> peopleNoDupsSet = new HashSet<String>();
325 List<Person> peopleNoDupsList = new ArrayList<Person>();
326
327 for (Iterator<Person> iter = people.iterator(); iter.hasNext(); ) {
328 Person person = iter.next();
329 if (peopleNoDupsSet.add(person.getEntityId() + person.getPrincipalId() + person.getPrincipalName())) {
330 peopleNoDupsList.add(person);
331 }
332 }
333
334 people.clear();
335 people.addAll(peopleNoDupsList);
336
337 return people;
338 }
339
340 @SuppressWarnings("unchecked")
341 protected List<Person> findPeopleInternal(Map<String,String> criteria, boolean unbounded ) {
342 // convert the criteria to a form that can be used by the ORM layer
343
344 //TODO convert this to the new criteria predicates
345 Map<String,String> entityCriteria = convertPersonPropertiesToEntityProperties( criteria );
346
347 Predicate predicate = PredicateUtils.convertMapToPredicate(entityCriteria);
348
349 QueryByCriteria.Builder queryBuilder = QueryByCriteria.Builder.create();
350 queryBuilder.setPredicates(predicate);
351
352 List<Person> people = new ArrayList<Person>();
353
354 EntityDefaultQueryResults qr = getIdentityService().findEntityDefaults( queryBuilder.build() );
355
356 for ( EntityDefault e : qr.getResults() ) {
357 // get to get all principals for the identity as well
358 for ( Principal p : e.getPrincipals() ) {
359 people.add( convertEntityToPerson( e, p ) );
360 }
361 }
362
363 return people;
364 }
365
366 public Map<String,String> convertPersonPropertiesToEntityProperties( Map<String,String> criteria ) {
367 if ( LOG.isDebugEnabled() ) {
368 LOG.debug( "convertPersonPropertiesToEntityProperties: " + criteria );
369 }
370 boolean nameCriteria = false;
371 boolean addressCriteria = false;
372 boolean externalIdentifierCriteria = false;
373 boolean affiliationCriteria = false;
374 boolean affiliationDefaultOnlyCriteria = false;
375 boolean phoneCriteria = false;
376 boolean emailCriteria = false;
377 boolean employeeIdCriteria = false;
378 // add base lookups for all person lookups
379 HashMap<String,String> newCriteria = new HashMap<String,String>();
380 newCriteria.putAll( baseLookupCriteria );
381
382 newCriteria.put( "entityTypeContactInfos.entityTypeCode", personEntityTypeLookupCriteria );
383
384 if ( criteria != null ) {
385 for ( String key : criteria.keySet() ) {
386 //check active radio button
387 if(key.equals(KIMPropertyConstants.Person.ACTIVE)) {
388 newCriteria.put(KIMPropertyConstants.Person.ACTIVE, criteria.get(KIMPropertyConstants.Person.ACTIVE));
389 } else {
390 // The following if statement enables the "both" button to work correctly.
391 if (!(criteria.containsKey(KIMPropertyConstants.Person.ACTIVE))) {
392 newCriteria.remove( KIMPropertyConstants.Person.ACTIVE );
393 }
394 }
395
396 // if no value was passed, skip the entry in the Map
397 if ( StringUtils.isEmpty( criteria.get(key) ) ) {
398 continue;
399 }
400 // check if the value needs to be encrypted
401 // handle encrypted external identifiers
402 if ( key.equals( KIMPropertyConstants.Person.EXTERNAL_ID ) && StringUtils.isNotBlank(criteria.get(key)) ) {
403 // look for a ext ID type property
404 if ( criteria.containsKey( KIMPropertyConstants.Person.EXTERNAL_IDENTIFIER_TYPE_CODE ) ) {
405 String extIdTypeCode = criteria.get(KIMPropertyConstants.Person.EXTERNAL_IDENTIFIER_TYPE_CODE);
406 if ( StringUtils.isNotBlank(extIdTypeCode) ) {
407 // if found, load that external ID Type via service
408 EntityExternalIdentifierType extIdType = getIdentityService().getExternalIdentifierType(extIdTypeCode);
409 // if that type needs to be encrypted, encrypt the value in the criteria map
410 if ( extIdType != null && extIdType.isEncryptionRequired() ) {
411 try {
412 criteria.put(key,
413 CoreApiServiceLocator.getEncryptionService().encrypt(criteria.get(key))
414 );
415 } catch (GeneralSecurityException ex) {
416 LOG.error("Unable to encrypt value for external ID search of type " + extIdTypeCode, ex );
417 }
418 }
419 }
420 }
421 }
422
423 // convert the property to the Entity data model
424 String entityProperty = criteriaConversion.get( key );
425 if ( entityProperty != null ) {
426 newCriteria.put( entityProperty, criteria.get( key ) );
427 } else {
428 entityProperty = key;
429 // just pass it through if no translation present
430 newCriteria.put( key, criteria.get( key ) );
431 }
432 // check if additional criteria are needed based on the types of properties specified
433 if ( isNameEntityCriteria( entityProperty ) ) {
434 nameCriteria = true;
435 }
436 if ( isExternalIdentifierEntityCriteria( entityProperty ) ) {
437 externalIdentifierCriteria = true;
438 }
439 if ( isAffiliationEntityCriteria( entityProperty ) ) {
440 affiliationCriteria = true;
441 }
442 if ( isAddressEntityCriteria( entityProperty ) ) {
443 addressCriteria = true;
444 }
445 if ( isPhoneEntityCriteria( entityProperty ) ) {
446 phoneCriteria = true;
447 }
448 if ( isEmailEntityCriteria( entityProperty ) ) {
449 emailCriteria = true;
450 }
451 if ( isEmployeeIdEntityCriteria( entityProperty ) ) {
452 employeeIdCriteria = true;
453 }
454 // special handling for the campus code, since that forces the query to look
455 // at the default affiliation record only
456 if ( key.equals( "campusCode" ) ) {
457 affiliationDefaultOnlyCriteria = true;
458 }
459 }
460
461 if ( nameCriteria ) {
462 newCriteria.put( ENTITY_NAME_PROPERTY_PREFIX + "active", "Y" );
463 newCriteria.put( ENTITY_NAME_PROPERTY_PREFIX + "defaultValue", "Y" );
464 //newCriteria.put(ENTITY_NAME_PROPERTY_PREFIX + "nameCode", "PRFR");//so we only display 1 result
465 }
466 if ( addressCriteria ) {
467 newCriteria.put( ENTITY_ADDRESS_PROPERTY_PREFIX + "active", "Y" );
468 newCriteria.put( ENTITY_ADDRESS_PROPERTY_PREFIX + "defaultValue", "Y" );
469 }
470 if ( phoneCriteria ) {
471 newCriteria.put( ENTITY_PHONE_PROPERTY_PREFIX + "active", "Y" );
472 newCriteria.put( ENTITY_PHONE_PROPERTY_PREFIX + "defaultValue", "Y" );
473 }
474 if ( emailCriteria ) {
475 newCriteria.put( ENTITY_EMAIL_PROPERTY_PREFIX + "active", "Y" );
476 newCriteria.put( ENTITY_EMAIL_PROPERTY_PREFIX + "defaultValue", "Y" );
477 }
478 if ( employeeIdCriteria ) {
479 newCriteria.put( ENTITY_EMPLOYEE_ID_PROPERTY_PREFIX + "active", "Y" );
480 newCriteria.put( ENTITY_EMPLOYEE_ID_PROPERTY_PREFIX + "primary", "Y" );
481 }
482 if ( affiliationCriteria ) {
483 newCriteria.put( ENTITY_AFFILIATION_PROPERTY_PREFIX + "active", "Y" );
484 }
485 if ( affiliationDefaultOnlyCriteria ) {
486 newCriteria.put( ENTITY_AFFILIATION_PROPERTY_PREFIX + "defaultValue", "Y" );
487 }
488 }
489
490 if ( LOG.isDebugEnabled() ) {
491 LOG.debug( "Converted: " + newCriteria );
492 }
493 return newCriteria;
494 }
495
496 protected boolean isNameEntityCriteria( String propertyName ) {
497 return propertyName.startsWith( ENTITY_NAME_PROPERTY_PREFIX );
498 }
499 protected boolean isAddressEntityCriteria( String propertyName ) {
500 return propertyName.startsWith( ENTITY_ADDRESS_PROPERTY_PREFIX );
501 }
502 protected boolean isPhoneEntityCriteria( String propertyName ) {
503 return propertyName.startsWith( ENTITY_PHONE_PROPERTY_PREFIX );
504 }
505 protected boolean isEmailEntityCriteria( String propertyName ) {
506 return propertyName.startsWith( ENTITY_EMAIL_PROPERTY_PREFIX );
507 }
508 protected boolean isEmployeeIdEntityCriteria( String propertyName ) {
509 return propertyName.startsWith( ENTITY_EMPLOYEE_ID_PROPERTY_PREFIX );
510 }
511 protected boolean isAffiliationEntityCriteria( String propertyName ) {
512 return propertyName.startsWith( ENTITY_AFFILIATION_PROPERTY_PREFIX );
513 }
514 protected boolean isExternalIdentifierEntityCriteria( String propertyName ) {
515 return propertyName.startsWith( ENTITY_EXT_ID_PROPERTY_PREFIX );
516 }
517
518 /**
519 * Get the entityTypeCode that can be associated with a Person. This will determine
520 * where EntityType-related data is pulled from within the KimEntity object. The codes
521 * in the list will be examined in the order present.
522 */
523 public List<String> getPersonEntityTypeCodes() {
524 return this.personEntityTypeCodes;
525 }
526
527 public void setPersonEntityTypeCodes(List<String> personEntityTypeCodes) {
528 this.personEntityTypeCodes = personEntityTypeCodes;
529 personEntityTypeLookupCriteria = null;
530 for ( String entityTypeCode : personEntityTypeCodes ) {
531 if ( personEntityTypeLookupCriteria == null ) {
532 personEntityTypeLookupCriteria = entityTypeCode;
533 } else {
534 personEntityTypeLookupCriteria = personEntityTypeLookupCriteria + "|" + entityTypeCode;
535 }
536 }
537 }
538
539
540 protected List<Person> getPeople( Collection<String> principalIds ) {
541 List<Person> people = new ArrayList<Person>( principalIds.size() );
542 for ( String principalId : principalIds ) {
543 people.add( getPerson(principalId) );
544 }
545 return people;
546 }
547
548 protected List<String> peopleToPrincipalIds( List<Person> people ) {
549 List<String> principalIds = new ArrayList<String>();
550
551 for ( Person person : people ) {
552 principalIds.add( person.getPrincipalId() );
553 }
554
555 return principalIds;
556 }
557
558 /**
559 * @see org.kuali.rice.kim.api.identity.PersonService#getPersonByExternalIdentifier(java.lang.String, java.lang.String)
560 */
561 public List<Person> getPersonByExternalIdentifier(String externalIdentifierTypeCode, String externalId) {
562 if (StringUtils.isBlank( externalIdentifierTypeCode ) || StringUtils.isBlank( externalId ) ) {
563 return null;
564 }
565 Map<String,String> criteria = new HashMap<String,String>( 2 );
566 criteria.put( KIMPropertyConstants.Person.EXTERNAL_IDENTIFIER_TYPE_CODE, externalIdentifierTypeCode );
567 criteria.put( KIMPropertyConstants.Person.EXTERNAL_ID, externalId );
568 return findPeople( criteria );
569 }
570
571 /**
572 * @see org.kuali.rice.kim.api.identity.PersonService#updatePersonIfNecessary(java.lang.String, org.kuali.rice.kim.api.identity.Person)
573 */
574 public Person updatePersonIfNecessary(String sourcePrincipalId, Person currentPerson ) {
575 if (currentPerson == null // no person set
576 || !StringUtils.equals(sourcePrincipalId, currentPerson.getPrincipalId() ) // principal ID mismatch
577 || currentPerson.getEntityId() == null ) { // syntheticially created Person object
578 Person person = getPerson( sourcePrincipalId );
579 // if a synthetically created person object is present, leave it - required for property derivation and the UI layer for
580 // setting the principal name
581 if ( person == null ) {
582 if ( currentPerson != null && currentPerson.getEntityId() == null ) {
583 return currentPerson;
584 }
585 }
586 // if both are null, create an empty object for property derivation
587 if ( person == null && currentPerson == null ) {
588 try {
589 return new PersonImpl();
590 } catch ( Exception ex ) {
591 LOG.error( "unable to instantiate an object of type: " + getPersonImplementationClass() + " - returning null", ex );
592 return null;
593 }
594 }
595 return person;
596 }
597 // otherwise, no need to change the given object
598 return currentPerson;
599 }
600
601 /**
602 * Builds a map containing entries from the passed in Map that do NOT represent properties on an embedded
603 * Person object.
604 */
605 private Map<String,String> getNonPersonSearchCriteria( BusinessObject bo, Map<String,String> fieldValues) {
606 Map<String,String> nonUniversalUserSearchCriteria = new HashMap<String,String>();
607 for ( String propertyName : fieldValues.keySet() ) {
608 if (!isPersonProperty(bo, propertyName)) {
609 nonUniversalUserSearchCriteria.put(propertyName, fieldValues.get(propertyName));
610 }
611 }
612 return nonUniversalUserSearchCriteria;
613 }
614
615
616 private boolean isPersonProperty(BusinessObject bo, String propertyName) {
617 try {
618 if ( ObjectUtils.isNestedAttribute( propertyName ) // is a nested property
619 && !StringUtils.contains(propertyName, "add.") ) {// exclude add line properties (due to path parsing problems in PropertyUtils.getPropertyType)
620 Class<?> type = PropertyUtils.getPropertyType(bo, ObjectUtils.getNestedAttributePrefix( propertyName ));
621 // property type indicates a Person object
622 if ( type != null ) {
623 return Person.class.isAssignableFrom(type);
624 }
625 LOG.warn( "Unable to determine type of nested property: " + bo.getClass().getName() + " / " + propertyName );
626 }
627 } catch (Exception ex) {
628 if ( LOG.isDebugEnabled() ) {
629 LOG.debug("Unable to determine if property on " + bo.getClass().getName() + " to a person object: " + propertyName, ex );
630 }
631 }
632 return false;
633 }
634
635 /**
636 * @see org.kuali.rice.kim.api.identity.PersonService#resolvePrincipalNamesToPrincipalIds(org.kuali.rice.krad.bo.BusinessObject, java.util.Map)
637 */
638 @SuppressWarnings("unchecked")
639 public Map<String,String> resolvePrincipalNamesToPrincipalIds(BusinessObject businessObject, Map<String,String> fieldValues) {
640 if ( fieldValues == null ) {
641 return null;
642 }
643 if ( businessObject == null ) {
644 return fieldValues;
645 }
646 StringBuffer resolvedPrincipalIdPropertyName = new StringBuffer();
647 // save off all criteria which are not references to Person properties
648 // leave person properties out so they can be resolved and replaced by this method
649 Map<String,String> processedFieldValues = getNonPersonSearchCriteria(businessObject, fieldValues);
650 for ( String propertyName : fieldValues.keySet() ) {
651 if ( !StringUtils.isBlank(fieldValues.get(propertyName)) // property has a value
652 && isPersonProperty(businessObject, propertyName) // is a property on a Person object
653 ) {
654 // strip off the prefix on the property
655 String personPropertyName = ObjectUtils.getNestedAttributePrimitive( propertyName );
656 // special case - the user ID
657 if ( StringUtils.equals( KIMPropertyConstants.Person.PRINCIPAL_NAME, personPropertyName) ) {
658 Class targetBusinessObjectClass = null;
659 BusinessObject targetBusinessObject = null;
660 resolvedPrincipalIdPropertyName.setLength( 0 ); // clear the buffer without requiring a new object allocation on each iteration
661 // get the property name up until the ".principalName"
662 // this should be a reference to the Person object attached to the BusinessObject
663 String personReferenceObjectPropertyName = ObjectUtils.getNestedAttributePrefix( propertyName );
664 // check if the person was nested within another BO under the master BO. If so, go up one more level
665 // otherwise, use the passed in BO class as the target class
666 if ( ObjectUtils.isNestedAttribute( personReferenceObjectPropertyName ) ) {
667 String targetBusinessObjectPropertyName = ObjectUtils.getNestedAttributePrefix( personReferenceObjectPropertyName );
668 targetBusinessObject = (BusinessObject)ObjectUtils.getPropertyValue( businessObject, targetBusinessObjectPropertyName );
669 if (targetBusinessObject != null) {
670 targetBusinessObjectClass = targetBusinessObject.getClass();
671 resolvedPrincipalIdPropertyName.append(targetBusinessObjectPropertyName).append(".");
672 } else {
673 LOG.error("Could not find target property '"+propertyName+"' in class "+businessObject.getClass().getName()+". Property value was null.");
674 }
675 } else { // not a nested Person property
676 targetBusinessObjectClass = businessObject.getClass();
677 targetBusinessObject = businessObject;
678 }
679
680 if (targetBusinessObjectClass != null) {
681 // use the relationship metadata in the KNS to determine the property on the
682 // host business object to put back into the map now that the principal ID
683 // (the value stored in application tables) has been resolved
684 String propName = ObjectUtils.getNestedAttributePrimitive( personReferenceObjectPropertyName );
685 DataObjectRelationship rel = getBusinessObjectMetaDataService().getBusinessObjectRelationship( targetBusinessObject, propName );
686 if ( rel != null ) {
687 String sourcePrimitivePropertyName = rel.getParentAttributeForChildAttribute(KIMPropertyConstants.Person.PRINCIPAL_ID);
688 resolvedPrincipalIdPropertyName.append(sourcePrimitivePropertyName);
689 // get the principal - for translation of the principalName to principalId
690 String principalName = fieldValues.get( propertyName );
691 Principal principal = getIdentityService().getPrincipalByPrincipalName( principalName );
692 if (principal != null ) {
693 processedFieldValues.put(resolvedPrincipalIdPropertyName.toString(), principal.getPrincipalId());
694 } else {
695 processedFieldValues.put(resolvedPrincipalIdPropertyName.toString(), null);
696 try {
697 // if the principalName is bad, then we need to clear out the Person object
698 // and base principalId property
699 // so that their values are no longer accidentally used or re-populate
700 // the object
701 ObjectUtils.setObjectProperty(targetBusinessObject, resolvedPrincipalIdPropertyName.toString(), null );
702 ObjectUtils.setObjectProperty(targetBusinessObject, propName, null );
703 ObjectUtils.setObjectProperty(targetBusinessObject, propName + ".principalName", principalName );
704 } catch ( Exception ex ) {
705 LOG.error( "Unable to blank out the person object after finding that the person with the given principalName does not exist.", ex );
706 }
707 }
708 } else {
709 LOG.error( "Missing relationship for " + propName + " on " + targetBusinessObjectClass.getName() );
710 }
711 } else { // no target BO class - the code below probably will not work
712 processedFieldValues.put(resolvedPrincipalIdPropertyName.toString(), null);
713 }
714 }
715 // if the property does not seem to match the definition of a Person property but it
716 // does end in principalName then...
717 // this is to handle the case where the user ID is on an ADD line - a case excluded from isPersonProperty()
718 } else if (propertyName.endsWith("." + KIMPropertyConstants.Person.PRINCIPAL_NAME)){
719 // if we're adding to a collection and we've got the principalName; let's populate universalUser
720 String principalName = fieldValues.get(propertyName);
721 if ( StringUtils.isNotEmpty( principalName ) ) {
722 String containerPropertyName = propertyName;
723 if (containerPropertyName.startsWith(KRADConstants.MAINTENANCE_ADD_PREFIX)) {
724 containerPropertyName = StringUtils.substringAfter( propertyName, KRADConstants.MAINTENANCE_ADD_PREFIX );
725 }
726 // get the class of the object that is referenced by the property name
727 // if this is not true then there's a principalName collection or primitive attribute
728 // directly on the BO on the add line, so we just ignore that since something is wrong here
729 if ( ObjectUtils.isNestedAttribute( containerPropertyName ) ) {
730 // the first part of the property is the collection name
731 String collectionName = StringUtils.substringBefore( containerPropertyName, "." );
732 // what is the class held by that collection?
733 // JHK: I don't like this. This assumes that this method is only used by the maintenance
734 // document service. If that will always be the case, this method should be moved over there.
735 Class<? extends BusinessObject> collectionBusinessObjectClass = getMaintenanceDocumentDictionaryService()
736 .getCollectionBusinessObjectClass(
737 getMaintenanceDocumentDictionaryService()
738 .getDocumentTypeName(businessObject.getClass()), collectionName);
739 if (collectionBusinessObjectClass != null) {
740 // we are adding to a collection; get the relationships for that object;
741 // is there one for personUniversalIdentifier?
742 List<DataObjectRelationship> relationships =
743 getBusinessObjectMetaDataService().getBusinessObjectRelationships( collectionBusinessObjectClass );
744 // JHK: this seems like a hack - looking at all relationships for a BO does not guarantee that we get the right one
745 // JHK: why not inspect the objects like above? Is it the property path problems because of the .add. portion?
746 for ( DataObjectRelationship rel : relationships ) {
747 String parentAttribute = rel.getParentAttributeForChildAttribute( KIMPropertyConstants.Person.PRINCIPAL_ID );
748 if ( parentAttribute == null ) {
749 continue;
750 }
751 // there is a relationship for personUserIdentifier; use that to find the universal user
752 processedFieldValues.remove( propertyName );
753 String fieldPrefix = StringUtils.substringBeforeLast( StringUtils.substringBeforeLast( propertyName, "." + KIMPropertyConstants.Person.PRINCIPAL_NAME ), "." );
754 String relatedPrincipalIdPropertyName = fieldPrefix + "." + parentAttribute;
755 // KR-683 Special handling for extension objects
756 if(EXTENSION.equals(StringUtils.substringAfterLast(fieldPrefix, ".")) && EXTENSION.equals(StringUtils.substringBefore(parentAttribute, ".")))
757 {
758 relatedPrincipalIdPropertyName = fieldPrefix + "." + StringUtils.substringAfter(parentAttribute, ".");
759 }
760 String currRelatedPersonPrincipalId = processedFieldValues.get(relatedPrincipalIdPropertyName);
761 if ( StringUtils.isBlank( currRelatedPersonPrincipalId ) ) {
762 Principal principal = getIdentityService().getPrincipalByPrincipalName( principalName );
763 if ( principal != null ) {
764 processedFieldValues.put(relatedPrincipalIdPropertyName, principal.getPrincipalId());
765 } else {
766 processedFieldValues.put(relatedPrincipalIdPropertyName, null);
767 }
768 }
769 } // relationship loop
770 } else {
771 if ( LOG.isDebugEnabled() ) {
772 LOG.debug( "Unable to determine class for collection referenced as part of property: " + containerPropertyName + " on " + businessObject.getClass().getName() );
773 }
774 }
775 } else {
776 if ( LOG.isDebugEnabled() ) {
777 LOG.debug( "Non-nested property ending with 'principalName': " + containerPropertyName + " on " + businessObject.getClass().getName() );
778 }
779 }
780 }
781 }
782 }
783 return processedFieldValues;
784 }
785
786 // OTHER METHODS
787
788 protected IdentityService getIdentityService() {
789 if ( identityService == null ) {
790 identityService = KimApiServiceLocator.getIdentityService();
791 }
792 return identityService;
793 }
794
795 protected RoleService getRoleService() {
796 if ( roleService == null ) {
797 roleService = KimApiServiceLocator.getRoleService();
798 }
799 return roleService;
800 }
801
802
803 public Class<? extends Person> getPersonImplementationClass() {
804 return PersonImpl.class;
805 }
806
807 protected BusinessObjectMetaDataService getBusinessObjectMetaDataService() {
808 if ( businessObjectMetaDataService == null ) {
809 businessObjectMetaDataService = KNSServiceLocator.getBusinessObjectMetaDataService();
810 }
811 return businessObjectMetaDataService;
812 }
813
814 protected MaintenanceDocumentDictionaryService getMaintenanceDocumentDictionaryService() {
815 if ( maintenanceDocumentDictionaryService == null ) {
816 maintenanceDocumentDictionaryService = KNSServiceLocator.getMaintenanceDocumentDictionaryService();
817 }
818 return maintenanceDocumentDictionaryService;
819 }
820 }