protected PrincipalResolutionResult convertPersonAttributesToPrincipal(final String extractedPrincipalId,
                                                                           final Optional<Principal> currentPrincipal,
                                                                           final Map<String, List<Object>> attributes) {
        val convertedAttributes = new LinkedHashMap<String, List<Object>>();
        attributes.forEach((key, attrValue) -> {
            val values = ((List<Object>) CollectionUtils.toCollection(attrValue, ArrayList.class))
                .stream()
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
            LOGGER.debug("Found attribute [{}] with value(s) [{}]", key, values);
            convertedAttributes.put(key, values);
        });

        val builder = PrincipalResolutionResult.builder();
        var principalId = extractedPrincipalId;

        if (StringUtils.isNotBlank(context.getPrincipalAttributeNames())) {
            val attrNames = org.springframework.util.StringUtils.commaDelimitedListToSet(context.getPrincipalAttributeNames());

            val principalIdAttributes = new LinkedHashMap<>(attributes);
            if (context.isUseCurrentPrincipalId() && currentPrincipal.isPresent()) {
                val currentPrincipalAttributes = currentPrincipal.get().getAttributes();
                LOGGER.trace("Merging current principal attributes [{}] with resolved attributes [{}]",
                    currentPrincipalAttributes, principalIdAttributes);
                context.getAttributeMerger().mergeAttributes(principalIdAttributes, currentPrincipalAttributes);
            }

            LOGGER.debug("Using principal attributes [{}] to determine principal id", principalIdAttributes);
            val result = attrNames.stream()
                .map(String::trim)
                .filter(principalIdAttributes::containsKey)
                .map(principalIdAttributes::get)
                .findFirst();

            if (result.isEmpty()) {
                LOGGER.warn("Principal resolution is set to resolve users via attribute(s) [{}], and yet "
                        + "the collection of attributes retrieved [{}] do not contain any of those attributes. This is "
                        + "likely due to misconfiguration and CAS will use [{}] as the final principal id",
                    context.getPrincipalAttributeNames(), principalIdAttributes.keySet(), principalId);
                builder.success(false);
            } else {
                val values = result.get();
                if (!values.isEmpty()) {
                    principalId = CollectionUtils.firstElement(values).map(Object::toString).orElseThrow();
                    LOGGER.debug("Found principal id attribute value [{}]", principalId);
                }
            }
        }
        return builder.principalId(principalId).attributes(convertedAttributes).build();
    }
Resolves principals by querying a data source using the Person Directory API.
The {@link Principal#getAttributes()} are populated by the results of the
query and the principal ID may optionally be set by proving an attribute whose first non-null value is used;
otherwise the credential ID is used for the principal ID.

@author Marvin S. Addison
@since 4.0.0
support/cas-server-support-person-directory-core/src/main/java/org/apereo/cas/authentication/principal/resolvers/PersonDirectoryPrincipalResolver.java