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 }