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