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