/** * Licensed to Apereo under one or more contributor license agreements. See the NOTICE file * distributed with this work for additional information regarding copyright ownership. Apereo * licenses this file to you under the Apache 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 the * following location: * * <p>http://www.apache.org/licenses/LICENSE-2.0 * * <p>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.apereo.portal.persondir; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.Validate; import org.jasig.services.persondir.IPersonAttributes; import org.jasig.services.persondir.support.AbstractDefaultAttributePersonAttributeDao; import org.jasig.services.persondir.support.AttributeNamedPersonImpl; import org.jasig.services.persondir.support.IUsernameAttributeProvider; import org.jasig.services.persondir.support.MultivaluedPersonAttributeUtils; import org.jasig.services.persondir.support.NamedPersonImpl; /** * LocalAccountPersonAttributeDao provides a person directory implementation that uses uPortal's * internal account store. This implementation overrides several methods of * AbstractQueryPersonAttributeDao to allow for the use of arbitrary user attributes without * requiring an administrator to add new entries to the result or query attribute mappings. * */ public class LocalAccountPersonAttributeDao extends AbstractDefaultAttributePersonAttributeDao { private ILocalAccountDao localAccountDao; private Map<String, Set<String>> resultAttributeMapping; private Set<String> possibleUserAttributes; private String unmappedUsernameAttribute = null; private String displayNameAttribute = "displayName"; private Map<String, Set<String>> queryAttributeMapping; /** * Set the local portal account DAO. * * @param localAccountDao */ public void setLocalAccountDao(ILocalAccountDao localAccountDao) { this.localAccountDao = localAccountDao; } public void setQueryAttributeMapping(final Map<String, ?> queryAttributeMapping) { final Map<String, Set<String>> parsedQueryAttributeMapping = MultivaluedPersonAttributeUtils.parseAttributeToAttributeMapping( queryAttributeMapping); if (parsedQueryAttributeMapping.containsKey("")) { throw new IllegalArgumentException( "The map from attribute names to attributes must not have any empty keys."); } this.queryAttributeMapping = parsedQueryAttributeMapping; } /** @return the resultAttributeMapping */ public Map<String, Set<String>> getResultAttributeMapping() { return resultAttributeMapping; } /** * Set the {@link Map} to use for mapping from a data layer name to an attribute name or {@link * Set} of attribute names. Data layer names that are specified but have null mappings will use * the column name for the attribute name. Data layer names that are not specified as keys in * this {@link Map} will be ignored. <br> * The passed {@link Map} must have keys of type {@link String} and values of type {@link * String} or a {@link Set} of {@link String}. * * @param resultAttributeMapping {@link Map} from column names to attribute names, may not be * null. * @throws IllegalArgumentException If the {@link Map} doesn't follow the rules stated above. * @see MultivaluedPersonAttributeUtils#parseAttributeToAttributeMapping(Map) */ public void setResultAttributeMapping(Map<String, ?> resultAttributeMapping) { final Map<String, Set<String>> parsedResultAttributeMapping = MultivaluedPersonAttributeUtils.parseAttributeToAttributeMapping( resultAttributeMapping); if (parsedResultAttributeMapping.containsKey("")) { throw new IllegalArgumentException( "The map from attribute names to attributes must not have any empty keys."); } final Collection<String> userAttributes = MultivaluedPersonAttributeUtils.flattenCollection( parsedResultAttributeMapping.values()); this.resultAttributeMapping = parsedResultAttributeMapping; this.possibleUserAttributes = Collections.unmodifiableSet(new LinkedHashSet<String>(userAttributes)); } /** @return the userNameAttribute */ public String getUnmappedUsernameAttribute() { return unmappedUsernameAttribute; } /** * The returned attribute to use as the userName for the mapped IPersons. If null the {@link * #setDefaultAttributeName(String)} value will be used and if that is null the {@link * AttributeNamedPersonImpl#DEFAULT_USER_NAME_ATTRIBUTE} value is used. * * @param userNameAttribute the userNameAttribute to set */ public void setUnmappedUsernameAttribute(String userNameAttribute) { this.unmappedUsernameAttribute = userNameAttribute; } /** * Return the list of all possible attribute names. This implementation queries the database to * provide a list of all mapped attribute names, plus all attribute keys currently in-use in the * database. * * @return Set */ public Set<String> getPossibleUserAttributeNames() { final Set<String> names = new HashSet<String>(); names.addAll(this.possibleUserAttributes); names.addAll(localAccountDao.getCurrentAttributeNames()); names.add(displayNameAttribute); return names; } /** * Return the list of all possible query attributes. This implementation queries the database to * provide a list of all mapped query attribute names, plus all attribute keys currently in-use * in the database. * * @return Set */ public Set<String> getAvailableQueryAttributes() { if (this.queryAttributeMapping == null) { return Collections.emptySet(); } return Collections.unmodifiableSet(this.queryAttributeMapping.keySet()); } /* (non-Javadoc) * @see org.jasig.services.persondir.IPersonAttributeDao#getPeopleWithMultivaluedAttributes(java.util.Map) */ public final Set<IPersonAttributes> getPeopleWithMultivaluedAttributes( Map<String, List<Object>> query) { Validate.notNull(query, "query may not be null."); //Generate the query to pass to the subclass final LocalAccountQuery queryBuilder = this.generateQuery(query); if (queryBuilder == null) { this.logger.debug( "No queryBuilder was generated for query " + query + ", null will be returned"); return null; } //Get the username from the query, if specified final IUsernameAttributeProvider usernameAttributeProvider = this.getUsernameAttributeProvider(); //Execute the query in the subclass final List<ILocalAccountPerson> unmappedPeople = localAccountDao.getPeople(queryBuilder); if (unmappedPeople == null) { return null; } //Map the attributes of the found people according to resultAttributeMapping if it is set final Set<IPersonAttributes> mappedPeople = new LinkedHashSet<IPersonAttributes>(); for (final ILocalAccountPerson unmappedPerson : unmappedPeople) { final IPersonAttributes mappedPerson = this.mapPersonAttributes(unmappedPerson); mappedPeople.add(mappedPerson); } return Collections.unmodifiableSet(mappedPeople); } protected LocalAccountQuery generateQuery(Map<String, List<Object>> query) { LocalAccountQuery queryBuilder = new LocalAccountQuery(); String userNameAttribute = this.getConfiguredUserNameAttribute(); for (final Map.Entry<String, List<Object>> queryEntry : query.entrySet()) { String attrName = queryEntry.getKey(); if (userNameAttribute.equals(attrName)) { String value = queryEntry.getValue().get(0).toString(); queryBuilder.setUserName(value); } else { List<String> values = new ArrayList<String>(); for (Object o : queryEntry.getValue()) { values.add(o.toString()); } queryBuilder.setAttribute(attrName, values); } } if (this.logger.isDebugEnabled()) { this.logger.debug( "Generated query builder '" + queryBuilder + "' from query Map " + query + "."); } return queryBuilder; } /** * This implementation uses the result attribute mapping to supplement, rather than replace, the * attributes returned from the database. */ protected IPersonAttributes mapPersonAttributes(final ILocalAccountPerson person) { final Map<String, List<Object>> mappedAttributes = new LinkedHashMap<String, List<Object>>(); mappedAttributes.putAll(person.getAttributes()); // map the user's username to the portal's username attribute in the // attribute map mappedAttributes.put( this.getUsernameAttributeProvider().getUsernameAttribute(), Collections.<Object>singletonList(person.getName())); // if the user does not have a display name attribute set, attempt // to build one from the first and last name attributes if (!mappedAttributes.containsKey(displayNameAttribute) || mappedAttributes.get(displayNameAttribute).size() == 0 || StringUtils.isBlank( (String) mappedAttributes.get(displayNameAttribute).get(0))) { final List<Object> firstNames = mappedAttributes.get("givenName"); final List<Object> lastNames = mappedAttributes.get("sn"); final StringBuilder displayName = new StringBuilder(); if (firstNames != null && firstNames.size() > 0) { displayName.append(firstNames.get(0)).append(" "); } if (lastNames != null && lastNames.size() > 0) { displayName.append(lastNames.get(0)); } mappedAttributes.put( displayNameAttribute, Collections.<Object>singletonList(displayName.toString())); } for (final Map.Entry<String, Set<String>> resultAttrEntry : this.getResultAttributeMapping().entrySet()) { final String dataKey = resultAttrEntry.getKey(); //Only map found data attributes if (mappedAttributes.containsKey(dataKey)) { Set<String> resultKeys = resultAttrEntry.getValue(); //If dataKey has no mapped resultKeys just use the dataKey if (resultKeys == null) { resultKeys = Collections.singleton(dataKey); } //Add the value to the mapped attributes for each mapped key final List<Object> value = mappedAttributes.get(dataKey); for (final String resultKey : resultKeys) { if (resultKey == null) { //TODO is this possible? if (!mappedAttributes.containsKey(dataKey)) { mappedAttributes.put(dataKey, value); } } else if (!mappedAttributes.containsKey(resultKey)) { mappedAttributes.put(resultKey, value); } } } } final IPersonAttributes newPerson; final String name = person.getName(); if (name != null) { newPerson = new NamedPersonImpl(name, mappedAttributes); } else { final String userNameAttribute = this.getConfiguredUserNameAttribute(); newPerson = new AttributeNamedPersonImpl(userNameAttribute, mappedAttributes); } return newPerson; } /** * @return The appropriate attribute to user for the user name. Since {@link * #getDefaultAttributeName()} should never return null this method should never return null * either. */ protected String getConfiguredUserNameAttribute() { //If configured explicitly use it if (this.unmappedUsernameAttribute != null) { return this.unmappedUsernameAttribute; } final IUsernameAttributeProvider usernameAttributeProvider = this.getUsernameAttributeProvider(); return usernameAttributeProvider.getUsernameAttribute(); } }