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 }