Java tutorial
/** * Copyright 2005-2014 The Kuali Foundation * * Licensed under the Educational Community License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.opensource.org/licenses/ecl2.php * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.kuali.rice.kim.impl.identity; import java.security.GeneralSecurityException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.beanutils.PropertyUtils; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.kuali.rice.core.api.CoreApiServiceLocator; import org.kuali.rice.core.api.criteria.CountFlag; import org.kuali.rice.core.api.criteria.Predicate; import org.kuali.rice.core.api.criteria.PredicateUtils; import org.kuali.rice.core.api.criteria.QueryByCriteria; import org.kuali.rice.kim.api.identity.IdentityService; import org.kuali.rice.kim.api.identity.Person; import org.kuali.rice.kim.api.identity.PersonService; import org.kuali.rice.kim.api.identity.entity.EntityDefault; import org.kuali.rice.kim.api.identity.entity.EntityDefaultQueryResults; import org.kuali.rice.kim.api.identity.external.EntityExternalIdentifierType; import org.kuali.rice.kim.api.identity.principal.Principal; import org.kuali.rice.kim.api.identity.type.EntityTypeContactInfoDefault; import org.kuali.rice.kim.api.role.RoleService; import org.kuali.rice.kim.api.services.KimApiServiceLocator; import org.kuali.rice.kim.impl.KIMPropertyConstants; import org.kuali.rice.kns.service.BusinessObjectMetaDataService; import org.kuali.rice.kns.service.KNSServiceLocator; import org.kuali.rice.kns.service.MaintenanceDocumentDictionaryService; import org.kuali.rice.krad.bo.BusinessObject; import org.kuali.rice.krad.bo.DataObjectRelationship; import org.kuali.rice.krad.data.DataObjectWrapper; import org.kuali.rice.krad.data.KradDataServiceLocator; import org.kuali.rice.krad.lookup.CollectionIncomplete; import org.kuali.rice.krad.util.KRADConstants; import org.kuali.rice.krad.util.KRADPropertyConstants; import org.kuali.rice.krad.util.KRADUtils; import org.springframework.beans.PropertyAccessorUtils; /** * This is a description of what this class does - kellerj don't forget to fill this in. * * @author Kuali Rice Team (rice.collab@kuali.org) * */ public class PersonServiceImpl implements PersonService { private static Logger LOG = Logger.getLogger(PersonServiceImpl.class); protected static final String ENTITY_EXT_ID_PROPERTY_PREFIX = "externalIdentifiers."; protected static final String ENTITY_AFFILIATION_PROPERTY_PREFIX = "affiliations."; protected static final String ENTITY_TYPE_PROPERTY_PREFIX = "entityTypeContactInfos."; protected static final String ENTITY_EMAIL_PROPERTY_PREFIX = "entityTypeContactInfos.emailAddresses."; protected static final String ENTITY_PHONE_PROPERTY_PREFIX = "entityTypeContactInfos.phoneNumbers."; protected static final String ENTITY_ADDRESS_PROPERTY_PREFIX = "entityTypeContactInfos.addresses."; protected static final String ENTITY_NAME_PROPERTY_PREFIX = "names."; protected static final String PRINCIPAL_PROPERTY_PREFIX = "principals."; protected static final String ENTITY_EMPLOYEE_ID_PROPERTY_PREFIX = "employmentInformation."; // KULRICE-4442 Special handling for extension objects protected static final String EXTENSION = "extension"; private IdentityService identityService; private RoleService roleService; private BusinessObjectMetaDataService businessObjectMetaDataService; private MaintenanceDocumentDictionaryService maintenanceDocumentDictionaryService; protected List<String> personEntityTypeCodes = new ArrayList<String>(4); // String that can be passed to the lookup framework to create an type = X OR type = Y criteria private String personEntityTypeLookupCriteria = null; protected Map<String, String> baseLookupCriteria = new HashMap<String, String>(); protected Map<String, String> criteriaConversion = new HashMap<String, String>(); protected ArrayList<String> personCachePropertyNames = new ArrayList<String>(); { // init the criteria which will need to be applied to every lookup against // the identity data tables baseLookupCriteria.put(KIMPropertyConstants.Person.ACTIVE, "Y"); baseLookupCriteria.put(ENTITY_TYPE_PROPERTY_PREFIX + KRADPropertyConstants.ACTIVE, "Y"); // create the field mappings between the Person object and the KimEntity object criteriaConversion.put(KIMPropertyConstants.Person.ENTITY_ID, KIMPropertyConstants.Entity.ID); criteriaConversion.put(KIMPropertyConstants.Person.ACTIVE, PRINCIPAL_PROPERTY_PREFIX + KRADPropertyConstants.ACTIVE); criteriaConversion.put(KIMPropertyConstants.Person.PRINCIPAL_ID, PRINCIPAL_PROPERTY_PREFIX + KIMPropertyConstants.Person.PRINCIPAL_ID); criteriaConversion.put(KIMPropertyConstants.Person.PRINCIPAL_NAME, PRINCIPAL_PROPERTY_PREFIX + KIMPropertyConstants.Person.PRINCIPAL_NAME); criteriaConversion.put(KIMPropertyConstants.Person.FIRST_NAME, "names.firstName"); criteriaConversion.put(KIMPropertyConstants.Person.LAST_NAME, "names.lastName"); criteriaConversion.put(KIMPropertyConstants.Person.MIDDLE_NAME, "names.middleName"); criteriaConversion.put(KIMPropertyConstants.Person.EMAIL_ADDRESS, "entityTypeContactInfos.emailAddresses.emailAddress"); criteriaConversion.put(KIMPropertyConstants.Person.PHONE_NUMBER, "entityTypeContactInfos.phoneNumbers.phoneNumber"); criteriaConversion.put(KIMPropertyConstants.Person.ADDRESS_LINE_1, "entityTypeContactInfos.addresses.line1"); criteriaConversion.put(KIMPropertyConstants.Person.ADDRESS_LINE_2, "entityTypeContactInfos.addresses.line2"); criteriaConversion.put(KIMPropertyConstants.Person.ADDRESS_LINE_3, "entityTypeContactInfos.addresses.line3"); criteriaConversion.put(KIMPropertyConstants.Person.CITY, "entityTypeContactInfos.addresses.city"); criteriaConversion.put(KIMPropertyConstants.Person.STATE_CODE, "entityTypeContactInfos.addresses.stateProvinceCode"); criteriaConversion.put(KIMPropertyConstants.Person.POSTAL_CODE, "entityTypeContactInfos.addresses.postalCode"); criteriaConversion.put(KIMPropertyConstants.Person.COUNTRY_CODE, "entityTypeContactInfos.addresses.countryCode"); criteriaConversion.put(KIMPropertyConstants.Person.CAMPUS_CODE, "affiliations.campusCode"); criteriaConversion.put(KIMPropertyConstants.Person.AFFILIATION_TYPE_CODE, "affiliations.affiliationTypeCode"); criteriaConversion.put(KIMPropertyConstants.Person.EXTERNAL_IDENTIFIER_TYPE_CODE, "externalIdentifiers.externalIdentifierTypeCode"); criteriaConversion.put(KIMPropertyConstants.Person.EXTERNAL_ID, "externalIdentifiers.externalId"); criteriaConversion.put(KIMPropertyConstants.Person.EMPLOYEE_TYPE_CODE, "employmentInformation.employeeTypeCode"); criteriaConversion.put(KIMPropertyConstants.Person.EMPLOYEE_STATUS_CODE, "employmentInformation.employeeStatusCode"); criteriaConversion.put(KIMPropertyConstants.Person.EMPLOYEE_ID, "employmentInformation.employeeId"); criteriaConversion.put(KIMPropertyConstants.Person.BASE_SALARY_AMOUNT, "employmentInformation.baseSalaryAmount"); criteriaConversion.put(KIMPropertyConstants.Person.PRIMARY_DEPARTMENT_CODE, "employmentInformation.primaryDepartmentCode"); personCachePropertyNames.add(KIMPropertyConstants.Person.PRINCIPAL_ID); personCachePropertyNames.add(KIMPropertyConstants.Person.PRINCIPAL_NAME); personCachePropertyNames.add(KIMPropertyConstants.Person.ENTITY_ID); personCachePropertyNames.add(KIMPropertyConstants.Person.FIRST_NAME); personCachePropertyNames.add(KIMPropertyConstants.Person.LAST_NAME); personCachePropertyNames.add(KIMPropertyConstants.Person.MIDDLE_NAME); personCachePropertyNames.add(KIMPropertyConstants.Person.CAMPUS_CODE); personCachePropertyNames.add(KIMPropertyConstants.Person.EMPLOYEE_ID); personCachePropertyNames.add(KIMPropertyConstants.Person.PRIMARY_DEPARTMENT_CODE); } /** * @see org.kuali.rice.kim.api.identity.PersonService#getPerson(java.lang.String) */ @Override public Person getPerson(String principalId) { if (StringUtils.isBlank(principalId)) { return null; } // get the corresponding principal final Principal principal = getIdentityService().getPrincipal(principalId); // get the identity if (principal != null) { final EntityDefault entity = getIdentityService().getEntityDefault(principal.getEntityId()); // convert the principal and identity to a Person // skip if the person was created from the DB cache if (entity != null) { return convertEntityToPerson(entity, principal); } } return null; } protected PersonImpl convertEntityToPerson(EntityDefault entity, Principal principal) { try { // get the EntityEntityType for the EntityType corresponding to a Person for (String entityTypeCode : personEntityTypeCodes) { EntityTypeContactInfoDefault entType = entity.getEntityType(entityTypeCode); // if no "person" identity type present for the given principal, skip to the next type in the list if (entType == null) { continue; } // attach the principal and identity objects // PersonImpl has logic to pull the needed elements from the KimEntity-related classes return new PersonImpl(principal, entity, entityTypeCode); } return null; } catch (Exception ex) { // allow runtime exceptions to pass through if (ex instanceof RuntimeException) { throw (RuntimeException) ex; } throw new RuntimeException("Problem building person object", ex); } } /** * @see org.kuali.rice.kim.api.identity.PersonService#getPersonByPrincipalName(java.lang.String) */ @Override public Person getPersonByPrincipalName(String principalName) { if (StringUtils.isBlank(principalName)) { return null; } // get the corresponding principal final Principal principal = getIdentityService().getPrincipalByPrincipalName(principalName); // get the identity if (principal != null) { final EntityDefault entity = getIdentityService().getEntityDefault(principal.getEntityId()); // convert the principal and identity to a Person if (entity != null) { return convertEntityToPerson(entity, principal); } } return null; } @Override public Person getPersonByEmployeeId(String employeeId) { if (StringUtils.isBlank(employeeId)) { return null; } final List<Person> people = findPeople( Collections.singletonMap(KIMPropertyConstants.Person.EMPLOYEE_ID, employeeId)); if (!people.isEmpty()) { return people.get(0); } // If no person was found above, check for inactive records EntityDefault entity = getIdentityService().getEntityDefaultByEmployeeId(employeeId); if (entity != null) { if (!entity.getPrincipals().isEmpty()) { Principal principal = getIdentityService() .getPrincipal(entity.getPrincipals().get(0).getPrincipalId()); if (principal != null) { return convertEntityToPerson(entity, principal); } } } return null; } /** * @see org.kuali.rice.kim.api.identity.PersonService#findPeople(Map) */ @Override public List<Person> findPeople(Map<String, String> criteria) { return findPeople(criteria, true); } /** * @see org.kuali.rice.kim.api.identity.PersonService#findPeople(java.util.Map, boolean) */ @Override public List<Person> findPeople(Map<String, String> criteria, boolean unbounded) { List<Person> people = null; // protect from NPEs if (criteria == null) { criteria = Collections.emptyMap(); } // make a copy so it can be modified safely in this method criteria = new HashMap<String, String>(criteria); // extract the role lookup parameters and then remove them since later code will not know what to do with them String roleName = criteria.get("lookupRoleName"); String namespaceCode = criteria.get("lookupRoleNamespaceCode"); criteria.remove("lookupRoleName"); criteria.remove("lookupRoleNamespaceCode"); if (StringUtils.isNotBlank(namespaceCode) && StringUtils.isNotBlank(roleName)) { Integer searchResultsLimit = org.kuali.rice.kns.lookup.LookupUtils .getSearchResultsLimit(PersonImpl.class); int searchResultsLimitInt = Integer.MAX_VALUE; if (searchResultsLimit != null) { searchResultsLimitInt = searchResultsLimit.intValue(); } if (LOG.isDebugEnabled()) { LOG.debug("Performing Person search including role filter: " + namespaceCode + "/" + roleName); } if (criteria.size() == 1 && criteria.containsKey(KIMPropertyConstants.Person.ACTIVE)) { // if only active is specified if (LOG.isDebugEnabled()) { LOG.debug("Only active criteria specified, running role search first"); } // in this case, run the role lookup first and pass those results to the person lookup Collection<String> principalIds = getRoleService().getRoleMemberPrincipalIds(namespaceCode, roleName, Collections.<String, String>emptyMap()); StringBuffer sb = new StringBuffer(principalIds.size() * 15); Iterator<String> pi = principalIds.iterator(); while (pi.hasNext()) { sb.append(pi.next()); if (pi.hasNext()) { sb.append('|'); } } // add the list of principal IDs to the lookup so that only matching Person objects are returned criteria.put(KIMPropertyConstants.Person.PRINCIPAL_ID, sb.toString()); people = findPeopleInternal(criteria, false); // can allow internal method to filter here since no more filtering necessary } else if (!criteria.isEmpty()) { // i.e., person criteria are specified if (LOG.isDebugEnabled()) { LOG.debug("Person criteria also specified, running that search first"); } // run the person lookup first people = findPeopleInternal(criteria, true); // get all, since may need to be filtered // TODO - now check if these people have the given role // build a principal list List<String> principalIds = peopleToPrincipalIds(people); // get sublist of principals that have the given roles principalIds = getRoleService().getPrincipalIdSubListWithRole(principalIds, namespaceCode, roleName, Collections.<String, String>emptyMap()); // re-convert into people objects, wrapping in CollectionIncomplete if needed if (!unbounded && principalIds.size() > searchResultsLimitInt) { int actualResultSize = principalIds.size(); // trim the list down before converting to people principalIds = new ArrayList<String>(principalIds).subList(0, searchResultsLimitInt); // yes, this is a little wasteful people = getPeople(principalIds); // convert the results to people people = new CollectionIncomplete<Person>(people.subList(0, searchResultsLimitInt), new Long(actualResultSize)); } else { people = getPeople(principalIds); } } else { // only role criteria specified if (LOG.isDebugEnabled()) { LOG.debug("No Person criteria specified - only using role service."); } // run the role criteria to get the principals with the role Collection<String> principalIds = getRoleService().getRoleMemberPrincipalIds(namespaceCode, roleName, Collections.<String, String>emptyMap()); if (!unbounded && principalIds.size() > searchResultsLimitInt) { int actualResultSize = principalIds.size(); // trim the list down before converting to people principalIds = new ArrayList<String>(principalIds).subList(0, searchResultsLimitInt); // yes, this is a little wasteful people = getPeople(principalIds); // convert the results to people people = new CollectionIncomplete<Person>(people.subList(0, searchResultsLimitInt), new Long(actualResultSize)); } else { people = getPeople(principalIds); // convert the results to people } } } else { if (LOG.isDebugEnabled()) { LOG.debug("No Role criteria specified, running person lookup as normal."); } people = findPeopleInternal(criteria, unbounded); } // The following change is for KULRICE-5694 - It prevents duplicate rows from being returned for the // person inquiry (In this case, duplicate meaning same entityId, principalId, and principalNm). // This allows for multiple rows to be returned if an entityID has more then one principal name // or more than one principal ID. Set<String> peopleNoDupsSet = new HashSet<String>(); List<Person> peopleNoDupsList = new ArrayList<Person>(); for (Iterator<Person> iter = people.iterator(); iter.hasNext();) { Person person = iter.next(); if (peopleNoDupsSet.add(person.getEntityId() + person.getPrincipalId() + person.getPrincipalName())) { peopleNoDupsList.add(person); } } people.clear(); people.addAll(peopleNoDupsList); return people; } @SuppressWarnings("unchecked") protected List<Person> findPeopleInternal(Map<String, String> criteria, boolean unbounded) { // convert the criteria to a form that can be used by the ORM layer //TODO convert this to the new criteria predicates Map<String, String> entityCriteria = convertPersonPropertiesToEntityProperties(criteria); Predicate predicate = PredicateUtils.convertMapToPredicate(entityCriteria); QueryByCriteria.Builder queryBuilder = QueryByCriteria.Builder.create(); queryBuilder.setPredicates(predicate); if (!unbounded) { Integer searchResultsLimit = org.kuali.rice.kns.lookup.LookupUtils .getSearchResultsLimit(PersonImpl.class); if (searchResultsLimit != null && searchResultsLimit >= 0) { queryBuilder.setMaxResults(searchResultsLimit); queryBuilder.setCountFlag(CountFlag.INCLUDE); } } List<Person> people = new ArrayList<Person>(); EntityDefaultQueryResults qr = getIdentityService().findEntityDefaults(queryBuilder.build()); if (qr.getResults().size() > 0) { for (EntityDefault e : qr.getResults()) { // get to get all principals for the identity as well for (Principal p : e.getPrincipals()) { people.add(convertEntityToPerson(e, p)); } } } else if (!qr.isMoreResultsAvailable() && entityCriteria.containsKey("principals.principalId")) { if (!(entityCriteria.containsKey(KIMPropertyConstants.Person.ACTIVE)) || (criteria.get(KIMPropertyConstants.Person.ACTIVE).equals("N"))) { String principalId = entityCriteria.get("principals.principalId"); try { EntityDefault entityDefault = getIdentityService().getEntityDefaultByPrincipalId(principalId); for (Principal p : entityDefault.getPrincipals()) { if (!p.isActive()) { people.add(convertEntityToPerson(entityDefault, p)); } } } catch (Exception e) { LOG.info("A principal Id of " + principalId + " dose not exist in the system"); } } } else if (!qr.isMoreResultsAvailable() && entityCriteria.containsKey("principals.principalName")) { if (!(entityCriteria.containsKey(KIMPropertyConstants.Person.ACTIVE)) || (criteria.get(KIMPropertyConstants.Person.ACTIVE).equals("N"))) { String principalNm = entityCriteria.get("principals.principalName"); try { EntityDefault entityDefault = getIdentityService().getEntityDefaultByPrincipalName(principalNm); for (Principal p : entityDefault.getPrincipals()) { if (!p.isActive()) { people.add(convertEntityToPerson(entityDefault, p)); } } } catch (Exception e) { LOG.info("A principal name of " + principalNm + " dose not exist in the system"); } } } return people; } public Map<String, String> convertPersonPropertiesToEntityProperties(Map<String, String> criteria) { if (LOG.isDebugEnabled()) { LOG.debug("convertPersonPropertiesToEntityProperties: " + criteria); } boolean nameCriteria = false; boolean addressCriteria = false; boolean externalIdentifierCriteria = false; boolean affiliationCriteria = false; boolean affiliationDefaultOnlyCriteria = false; boolean phoneCriteria = false; boolean emailCriteria = false; boolean employeeIdCriteria = false; // add base lookups for all person lookups HashMap<String, String> newCriteria = new HashMap<String, String>(); newCriteria.putAll(baseLookupCriteria); newCriteria.put("entityTypeContactInfos.entityTypeCode", personEntityTypeLookupCriteria); if (criteria != null) { for (String key : criteria.keySet()) { //check active radio button if (key.equals(KIMPropertyConstants.Person.ACTIVE)) { newCriteria.put(criteriaConversion.get(KIMPropertyConstants.Person.ACTIVE), criteria.get(KIMPropertyConstants.Person.ACTIVE)); } else { // The following if statement enables the "both" button to work correctly. if (!(criteria.containsKey(KIMPropertyConstants.Person.ACTIVE))) { newCriteria.remove(KIMPropertyConstants.Person.ACTIVE); } } // if no value was passed, skip the entry in the Map if (StringUtils.isEmpty(criteria.get(key))) { continue; } // check if the value needs to be encrypted // handle encrypted external identifiers if (key.equals(KIMPropertyConstants.Person.EXTERNAL_ID) && StringUtils.isNotBlank(criteria.get(key))) { // look for a ext ID type property if (criteria.containsKey(KIMPropertyConstants.Person.EXTERNAL_IDENTIFIER_TYPE_CODE)) { String extIdTypeCode = criteria .get(KIMPropertyConstants.Person.EXTERNAL_IDENTIFIER_TYPE_CODE); if (StringUtils.isNotBlank(extIdTypeCode)) { // if found, load that external ID Type via service EntityExternalIdentifierType extIdType = getIdentityService() .getExternalIdentifierType(extIdTypeCode); // if that type needs to be encrypted, encrypt the value in the criteria map if (extIdType != null && extIdType.isEncryptionRequired()) { try { if (CoreApiServiceLocator.getEncryptionService().isEnabled()) { criteria.put(key, CoreApiServiceLocator.getEncryptionService() .encrypt(criteria.get(key))); } } catch (GeneralSecurityException ex) { LOG.error("Unable to encrypt value for external ID search of type " + extIdTypeCode, ex); } } } } } // convert the property to the Entity data model String entityProperty = criteriaConversion.get(key); if (entityProperty != null) { newCriteria.put(entityProperty, criteria.get(key)); } else { entityProperty = key; // just pass it through if no translation present newCriteria.put(key, criteria.get(key)); } // check if additional criteria are needed based on the types of properties specified if (isNameEntityCriteria(entityProperty)) { nameCriteria = true; } if (isExternalIdentifierEntityCriteria(entityProperty)) { externalIdentifierCriteria = true; } if (isAffiliationEntityCriteria(entityProperty)) { affiliationCriteria = true; } if (isAddressEntityCriteria(entityProperty)) { addressCriteria = true; } if (isPhoneEntityCriteria(entityProperty)) { phoneCriteria = true; } if (isEmailEntityCriteria(entityProperty)) { emailCriteria = true; } if (isEmployeeIdEntityCriteria(entityProperty)) { employeeIdCriteria = true; } // special handling for the campus code, since that forces the query to look // at the default affiliation record only if (key.equals("campusCode")) { affiliationDefaultOnlyCriteria = true; } } if (nameCriteria) { newCriteria.put(ENTITY_NAME_PROPERTY_PREFIX + "active", "Y"); newCriteria.put(ENTITY_NAME_PROPERTY_PREFIX + "defaultValue", "Y"); //newCriteria.put(ENTITY_NAME_PROPERTY_PREFIX + "nameCode", "PRFR");//so we only display 1 result } if (addressCriteria) { newCriteria.put(ENTITY_ADDRESS_PROPERTY_PREFIX + "active", "Y"); newCriteria.put(ENTITY_ADDRESS_PROPERTY_PREFIX + "defaultValue", "Y"); } if (phoneCriteria) { newCriteria.put(ENTITY_PHONE_PROPERTY_PREFIX + "active", "Y"); newCriteria.put(ENTITY_PHONE_PROPERTY_PREFIX + "defaultValue", "Y"); } if (emailCriteria) { newCriteria.put(ENTITY_EMAIL_PROPERTY_PREFIX + "active", "Y"); newCriteria.put(ENTITY_EMAIL_PROPERTY_PREFIX + "defaultValue", "Y"); } if (employeeIdCriteria) { newCriteria.put(ENTITY_EMPLOYEE_ID_PROPERTY_PREFIX + "active", "Y"); newCriteria.put(ENTITY_EMPLOYEE_ID_PROPERTY_PREFIX + "primary", "Y"); } if (affiliationCriteria) { newCriteria.put(ENTITY_AFFILIATION_PROPERTY_PREFIX + "active", "Y"); } if (affiliationDefaultOnlyCriteria) { newCriteria.put(ENTITY_AFFILIATION_PROPERTY_PREFIX + "defaultValue", "Y"); } } if (LOG.isDebugEnabled()) { LOG.debug("Converted: " + newCriteria); } return newCriteria; } protected boolean isNameEntityCriteria(String propertyName) { return propertyName.startsWith(ENTITY_NAME_PROPERTY_PREFIX); } protected boolean isAddressEntityCriteria(String propertyName) { return propertyName.startsWith(ENTITY_ADDRESS_PROPERTY_PREFIX); } protected boolean isPhoneEntityCriteria(String propertyName) { return propertyName.startsWith(ENTITY_PHONE_PROPERTY_PREFIX); } protected boolean isEmailEntityCriteria(String propertyName) { return propertyName.startsWith(ENTITY_EMAIL_PROPERTY_PREFIX); } protected boolean isEmployeeIdEntityCriteria(String propertyName) { return propertyName.startsWith(ENTITY_EMPLOYEE_ID_PROPERTY_PREFIX); } protected boolean isAffiliationEntityCriteria(String propertyName) { return propertyName.startsWith(ENTITY_AFFILIATION_PROPERTY_PREFIX); } protected boolean isExternalIdentifierEntityCriteria(String propertyName) { return propertyName.startsWith(ENTITY_EXT_ID_PROPERTY_PREFIX); } /** * Get the entityTypeCode that can be associated with a Person. This will determine * where EntityType-related data is pulled from within the KimEntity object. The codes * in the list will be examined in the order present. */ public List<String> getPersonEntityTypeCodes() { return this.personEntityTypeCodes; } public void setPersonEntityTypeCodes(List<String> personEntityTypeCodes) { this.personEntityTypeCodes = personEntityTypeCodes; personEntityTypeLookupCriteria = null; for (String entityTypeCode : personEntityTypeCodes) { if (personEntityTypeLookupCriteria == null) { personEntityTypeLookupCriteria = entityTypeCode; } else { personEntityTypeLookupCriteria = personEntityTypeLookupCriteria + "|" + entityTypeCode; } } } protected List<Person> getPeople(Collection<String> principalIds) { List<Person> people = new ArrayList<Person>(principalIds.size()); for (String principalId : principalIds) { people.add(getPerson(principalId)); } return people; } protected List<String> peopleToPrincipalIds(List<Person> people) { List<String> principalIds = new ArrayList<String>(); for (Person person : people) { principalIds.add(person.getPrincipalId()); } return principalIds; } /** * @see org.kuali.rice.kim.api.identity.PersonService#getPersonByExternalIdentifier(java.lang.String, java.lang.String) */ @Override public List<Person> getPersonByExternalIdentifier(String externalIdentifierTypeCode, String externalId) { if (StringUtils.isBlank(externalIdentifierTypeCode) || StringUtils.isBlank(externalId)) { return null; } Map<String, String> criteria = new HashMap<String, String>(2); criteria.put(KIMPropertyConstants.Person.EXTERNAL_IDENTIFIER_TYPE_CODE, externalIdentifierTypeCode); criteria.put(KIMPropertyConstants.Person.EXTERNAL_ID, externalId); return findPeople(criteria); } /** * @see org.kuali.rice.kim.api.identity.PersonService#updatePersonIfNecessary(java.lang.String, org.kuali.rice.kim.api.identity.Person) */ @Override public Person updatePersonIfNecessary(String sourcePrincipalId, Person currentPerson) { if (currentPerson == null // no person set || !StringUtils.equals(sourcePrincipalId, currentPerson.getPrincipalId()) // principal ID mismatch || currentPerson.getEntityId() == null) { // syntheticially created Person object Person person = getPerson(sourcePrincipalId); // if a synthetically created person object is present, leave it - required for property derivation and the UI layer for // setting the principal name if (person == null) { if (currentPerson != null && currentPerson.getEntityId() == null) { return currentPerson; } } // if both are null, create an empty object for property derivation if (person == null && currentPerson == null) { try { return new PersonImpl(); } catch (Exception ex) { LOG.error("unable to instantiate an object of type: " + getPersonImplementationClass() + " - returning null", ex); return null; } } return person; } // otherwise, no need to change the given object return currentPerson; } /** * Builds a map containing entries from the passed in Map that do NOT represent properties on an embedded * Person object. */ private Map<String, String> getNonPersonSearchCriteria(Object bo, Map<String, String> fieldValues) { Map<String, String> nonUniversalUserSearchCriteria = new HashMap<String, String>(); for (String propertyName : fieldValues.keySet()) { if (!isPersonProperty(bo, propertyName)) { nonUniversalUserSearchCriteria.put(propertyName, fieldValues.get(propertyName)); } } return nonUniversalUserSearchCriteria; } private boolean isPersonProperty(Object bo, String propertyName) { try { if (PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName) // is a nested property && !StringUtils.contains(propertyName, "add.")) {// exclude add line properties (due to path parsing problems in PropertyUtils.getPropertyType) int lastIndex = PropertyAccessorUtils.getLastNestedPropertySeparatorIndex(propertyName); String propertyTypeName = lastIndex != -1 ? StringUtils.substring(propertyName, 0, lastIndex) : StringUtils.EMPTY; Class<?> type = PropertyUtils.getPropertyType(bo, propertyTypeName); // property type indicates a Person object if (type != null) { return Person.class.isAssignableFrom(type); } LOG.warn("Unable to determine type of nested property: " + bo.getClass().getName() + " / " + propertyName); } } catch (Exception ex) { if (LOG.isDebugEnabled()) { LOG.debug("Unable to determine if property on " + bo.getClass().getName() + " to a person object: " + propertyName, ex); } } return false; } /** * @see org.kuali.rice.kim.api.identity.PersonService#resolvePrincipalNamesToPrincipalIds(org.kuali.rice.krad.bo.BusinessObject, java.util.Map) */ @Override @SuppressWarnings("unchecked") public Map<String, String> resolvePrincipalNamesToPrincipalIds(BusinessObject businessObject, Map<String, String> fieldValues) { if (fieldValues == null) { return null; } if (businessObject == null) { return fieldValues; } StringBuffer resolvedPrincipalIdPropertyName = new StringBuffer(); // save off all criteria which are not references to Person properties // leave person properties out so they can be resolved and replaced by this method Map<String, String> processedFieldValues = getNonPersonSearchCriteria(businessObject, fieldValues); for (String propertyName : fieldValues.keySet()) { if (!StringUtils.isBlank(fieldValues.get(propertyName)) // property has a value && isPersonProperty(businessObject, propertyName) // is a property on a Person object ) { // strip off the prefix on the property int lastPropertyIndex = PropertyAccessorUtils.getLastNestedPropertySeparatorIndex(propertyName); String personPropertyName = lastPropertyIndex != -1 ? StringUtils.substring(propertyName, lastPropertyIndex + 1) : propertyName; // special case - the user ID if (StringUtils.equals(KIMPropertyConstants.Person.PRINCIPAL_NAME, personPropertyName)) { Class targetBusinessObjectClass = null; BusinessObject targetBusinessObject = null; resolvedPrincipalIdPropertyName.setLength(0); // clear the buffer without requiring a new object allocation on each iteration // get the property name up until the ".principalName" // this should be a reference to the Person object attached to the BusinessObject String personReferenceObjectPropertyName = lastPropertyIndex != -1 ? StringUtils.substring(propertyName, 0, lastPropertyIndex) : StringUtils.EMPTY; // check if the person was nested within another BO under the master BO. If so, go up one more level // otherwise, use the passed in BO class as the target class if (PropertyAccessorUtils.isNestedOrIndexedProperty(personReferenceObjectPropertyName)) { int lastTargetIndex = PropertyAccessorUtils .getLastNestedPropertySeparatorIndex(personReferenceObjectPropertyName); String targetBusinessObjectPropertyName = lastTargetIndex != -1 ? StringUtils.substring(personReferenceObjectPropertyName, 0, lastTargetIndex) : StringUtils.EMPTY; DataObjectWrapper<BusinessObject> wrapper = KradDataServiceLocator.getDataObjectService() .wrap(businessObject); targetBusinessObject = (BusinessObject) wrapper .getPropertyValueNullSafe(targetBusinessObjectPropertyName); if (targetBusinessObject != null) { targetBusinessObjectClass = targetBusinessObject.getClass(); resolvedPrincipalIdPropertyName.append(targetBusinessObjectPropertyName).append("."); } else { LOG.error("Could not find target property '" + propertyName + "' in class " + businessObject.getClass().getName() + ". Property value was null."); } } else { // not a nested Person property targetBusinessObjectClass = businessObject.getClass(); targetBusinessObject = businessObject; } if (targetBusinessObjectClass != null) { // use the relationship metadata in the KNS to determine the property on the // host business object to put back into the map now that the principal ID // (the value stored in application tables) has been resolved int lastIndex = PropertyAccessorUtils .getLastNestedPropertySeparatorIndex(personReferenceObjectPropertyName); String propName = lastIndex != -1 ? StringUtils.substring(personReferenceObjectPropertyName, lastIndex + 1) : personReferenceObjectPropertyName; DataObjectRelationship rel = getBusinessObjectMetaDataService() .getBusinessObjectRelationship(targetBusinessObject, propName); if (rel != null) { String sourcePrimitivePropertyName = rel .getParentAttributeForChildAttribute(KIMPropertyConstants.Person.PRINCIPAL_ID); resolvedPrincipalIdPropertyName.append(sourcePrimitivePropertyName); // get the principal - for translation of the principalName to principalId String principalName = fieldValues.get(propertyName); Principal principal = getIdentityService().getPrincipalByPrincipalName(principalName); if (principal != null) { processedFieldValues.put(resolvedPrincipalIdPropertyName.toString(), principal.getPrincipalId()); } else { processedFieldValues.put(resolvedPrincipalIdPropertyName.toString(), null); try { // if the principalName is bad, then we need to clear out the Person object // and base principalId property // so that their values are no longer accidentally used or re-populate // the object KRADUtils.setObjectProperty(targetBusinessObject, resolvedPrincipalIdPropertyName.toString(), null); KRADUtils.setObjectProperty(targetBusinessObject, propName, null); KRADUtils.setObjectProperty(targetBusinessObject, propName + ".principalName", principalName); } catch (Exception ex) { LOG.error( "Unable to blank out the person object after finding that the person with the given principalName does not exist.", ex); } } } else { LOG.error("Missing relationship for " + propName + " on " + targetBusinessObjectClass.getName()); } } else { // no target BO class - the code below probably will not work processedFieldValues.put(resolvedPrincipalIdPropertyName.toString(), null); } } // if the property does not seem to match the definition of a Person property but it // does end in principalName then... // this is to handle the case where the user ID is on an ADD line - a case excluded from isPersonProperty() } else if (propertyName.endsWith("." + KIMPropertyConstants.Person.PRINCIPAL_NAME)) { // if we're adding to a collection and we've got the principalName; let's populate universalUser String principalName = fieldValues.get(propertyName); if (StringUtils.isNotEmpty(principalName)) { String containerPropertyName = propertyName; if (containerPropertyName.startsWith(KRADConstants.MAINTENANCE_ADD_PREFIX)) { containerPropertyName = StringUtils.substringAfter(propertyName, KRADConstants.MAINTENANCE_ADD_PREFIX); } // get the class of the object that is referenced by the property name // if this is not true then there's a principalName collection or primitive attribute // directly on the BO on the add line, so we just ignore that since something is wrong here if (PropertyAccessorUtils.isNestedOrIndexedProperty(containerPropertyName)) { // the first part of the property is the collection name String collectionName = StringUtils.substringBefore(containerPropertyName, "."); // what is the class held by that collection? // JHK: I don't like this. This assumes that this method is only used by the maintenance // document service. If that will always be the case, this method should be moved over there. Class<? extends BusinessObject> collectionBusinessObjectClass = getMaintenanceDocumentDictionaryService() .getCollectionBusinessObjectClass(getMaintenanceDocumentDictionaryService() .getDocumentTypeName(businessObject.getClass()), collectionName); if (collectionBusinessObjectClass != null) { // we are adding to a collection; get the relationships for that object; // is there one for personUniversalIdentifier? List<DataObjectRelationship> relationships = getBusinessObjectMetaDataService() .getBusinessObjectRelationships(collectionBusinessObjectClass); // JHK: this seems like a hack - looking at all relationships for a BO does not guarantee that we get the right one // JHK: why not inspect the objects like above? Is it the property path problems because of the .add. portion? for (DataObjectRelationship rel : relationships) { String parentAttribute = rel.getParentAttributeForChildAttribute( KIMPropertyConstants.Person.PRINCIPAL_ID); if (parentAttribute == null) { continue; } // there is a relationship for personUserIdentifier; use that to find the universal user processedFieldValues.remove(propertyName); String fieldPrefix = StringUtils .substringBeforeLast(StringUtils.substringBeforeLast(propertyName, "." + KIMPropertyConstants.Person.PRINCIPAL_NAME), "."); String relatedPrincipalIdPropertyName = fieldPrefix + "." + parentAttribute; // KR-683 Special handling for extension objects if (EXTENSION.equals(StringUtils.substringAfterLast(fieldPrefix, ".")) && EXTENSION.equals(StringUtils.substringBefore(parentAttribute, "."))) { relatedPrincipalIdPropertyName = fieldPrefix + "." + StringUtils.substringAfter(parentAttribute, "."); } String currRelatedPersonPrincipalId = processedFieldValues .get(relatedPrincipalIdPropertyName); if (StringUtils.isBlank(currRelatedPersonPrincipalId)) { Principal principal = getIdentityService() .getPrincipalByPrincipalName(principalName); if (principal != null) { processedFieldValues.put(relatedPrincipalIdPropertyName, principal.getPrincipalId()); } else { processedFieldValues.put(relatedPrincipalIdPropertyName, null); } } } // relationship loop } else { if (LOG.isDebugEnabled()) { LOG.debug( "Unable to determine class for collection referenced as part of property: " + containerPropertyName + " on " + businessObject.getClass().getName()); } } } else { if (LOG.isDebugEnabled()) { LOG.debug("Non-nested property ending with 'principalName': " + containerPropertyName + " on " + businessObject.getClass().getName()); } } } } } return processedFieldValues; } // OTHER METHODS protected IdentityService getIdentityService() { if (identityService == null) { identityService = KimApiServiceLocator.getIdentityService(); } return identityService; } protected RoleService getRoleService() { if (roleService == null) { roleService = KimApiServiceLocator.getRoleService(); } return roleService; } @Override public Class<? extends Person> getPersonImplementationClass() { return PersonImpl.class; } protected BusinessObjectMetaDataService getBusinessObjectMetaDataService() { if (businessObjectMetaDataService == null) { businessObjectMetaDataService = KNSServiceLocator.getBusinessObjectMetaDataService(); } return businessObjectMetaDataService; } protected MaintenanceDocumentDictionaryService getMaintenanceDocumentDictionaryService() { if (maintenanceDocumentDictionaryService == null) { maintenanceDocumentDictionaryService = KNSServiceLocator.getMaintenanceDocumentDictionaryService(); } return maintenanceDocumentDictionaryService; } }