Java tutorial
/** * 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: * * http://www.apache.org/licenses/LICENSE-2.0 * * 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.services.persondir.support.jdbc; import com.google.common.collect.MapMaker; import org.apereo.services.persondir.IPersonAttributeDao; import org.apereo.services.persondir.IPersonAttributes; import org.apereo.services.persondir.support.MultivaluedPersonAttributeUtils; import org.apereo.services.persondir.support.NamedPersonImpl; import org.springframework.jdbc.BadSqlGrammarException; import org.springframework.jdbc.core.RowMapper; import javax.sql.DataSource; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * An {@link IPersonAttributeDao} * implementation that maps attribute names and values from name and value column * pairs. This is usefull if user attributes are stored in a table like:<br> * <table border="1" summary=""> * <tr> * <th>USER_NM</th> * <th>ATTR_NM</th> * <th>ATTR_VL</th> * </tr> * <tr> * <td>jstudent</td> * <td>name.given</td> * <td>joe</td> * </tr> * <tr> * <td>jstudent</td> * <td>name.family</td> * <td>student</td> * </tr> * <tr> * <td>badvisor</td> * <td>name.given</td> * <td>bob</td> * </tr> * <tr> * <td>badvisor</td> * <td>name.family</td> * <td>advisor</td> * </tr> * </table> * * <br> * * This class expects 1 to N row results for a query, with each row containing 1 to N name * value attribute mappings and the userName of the user the attributes are for. This contrasts * {@link SingleRowJdbcPersonAttributeDao} which expects * a single row result for a user query. <br> * * <br> * <br> * Configuration: * <table border="1" summary=""> * <tr> * <th align="left">Property</th> * <th align="left">Description</th> * <th align="left">Required</th> * <th align="left">Default</th> * </tr> * <tr> * <td align="right" valign="top">nameValueColumnMappings</td> * <td> * A {@link Map} of attribute name columns to attribute value columns. A single result row can have multiple * name columns and multiple value columns associated with each name. The values of the {@link Map} can be * either {@link String} or {@link Collection} of String. * </td> * <td valign="top">Yes</td> * <td valign="top">null</td> * </tr> * </table> * * @author andrew.petro@yale.edu * @author Eric Dalquist <a href="mailto:edalquist@unicon.net">edalquist@unicon.net</a> * @since uPortal 2.5 */ public class MultiRowJdbcPersonAttributeDao extends AbstractJdbcPersonAttributeDao<Map<String, Object>> { private static final RowMapper<Map<String, Object>> MAPPER = new ColumnMapParameterizedRowMapper(); /** * {@link Map} of columns from a name column to value columns. * Keys are Strings, Values are Strings or List of Strings */ private Map<String, Set<String>> nameValueColumnMappings = null; public MultiRowJdbcPersonAttributeDao() { super(); } /** * Creates a new MultiRowJdbcPersonAttributeDao specifying the DataSource and SQL to use. * * @param ds The DataSource to get connections from for executing queries, may not be null. * @param sql The SQL to execute for user attributes, may not be null. */ public MultiRowJdbcPersonAttributeDao(final DataSource ds, final String sql) { super(ds, sql); } /** * @return The Map of name column to value column(s). */ public Map<String, Set<String>> getNameValueColumnMappings() { return this.nameValueColumnMappings; } /** * The {@link Map} of columns from a name column to value columns. Keys are Strings, * Values are Strings or {@link java.util.List} of Strings. * * @param nameValueColumnMap The Map of name column to value column(s). */ public void setNameValueColumnMappings(final Map<String, ?> nameValueColumnMap) { if (nameValueColumnMap == null) { this.nameValueColumnMappings = null; } else { final Map<String, Set<String>> mappings = MultivaluedPersonAttributeUtils .parseAttributeToAttributeMapping(nameValueColumnMap); if (mappings.containsValue(null)) { throw new IllegalArgumentException("nameValueColumnMap may not have null values"); } this.nameValueColumnMappings = mappings; } } /* (non-Javadoc) * @see org.jasig.services.persondir.support.jdbc.AbstractJdbcPersonAttributeDao#getRowMapper() */ @Override protected RowMapper<Map<String, Object>> getRowMapper() { return MAPPER; } @Override @SuppressWarnings("unchecked") protected List<IPersonAttributes> parseAttributeMapFromResults(final List<Map<String, Object>> queryResults, final String queryUserName) { final Map<String, Map<String, List<Object>>> peopleAttributesBuilder = new MapMaker().makeMap(); final String userNameAttribute = this.getConfiguredUserNameAttribute(); for (final Map<String, Object> queryResult : queryResults) { final String userName; // Choose a username from the best available option if (this.isUserNameAttributeConfigured() && queryResult.containsKey(userNameAttribute)) { // Option #1: An attribute is named explicitly in the config, // and that attribute is present in the results from LDAP; use it final Object userNameValue = queryResult.get(userNameAttribute); userName = userNameValue.toString(); } else if (queryUserName != null) { // Option #2: Use the userName attribute provided in the query // parameters. (NB: I'm not entirely sure this choice is // preferable to Option #3. Keeping it because it most closely // matches the legacy behavior there the new option -- Option #1 // -- doesn't apply. ~drewwills) userName = queryUserName; } else if (queryResult.containsKey(userNameAttribute)) { // Option #3: Create the IPersonAttributes useing the default // userName attribute, which we know to be present final Object userNameValue = queryResult.get(userNameAttribute); userName = userNameValue.toString(); } else { throw new BadSqlGrammarException( "No userName column named '" + userNameAttribute + "' exists in result set and no userName provided in query Map", this.getQueryTemplate(), null); } final Map<String, List<Object>> attributes = peopleAttributesBuilder.computeIfAbsent(userName, key -> new LinkedHashMap<String, List<Object>>()); //Iterate over each attribute column mapping to get the data from the row for (final Map.Entry<String, Set<String>> columnMapping : this.nameValueColumnMappings.entrySet()) { final String keyColumn = columnMapping.getKey(); //Get the attribute name for the specified column final Object attrNameObj = queryResult.get(keyColumn); if (attrNameObj == null && !queryResult.containsKey(keyColumn)) { throw new BadSqlGrammarException( "No attribute key column named '" + keyColumn + "' exists in result set", this.getQueryTemplate(), null); } final String attrName = String.valueOf(attrNameObj); //Get the columns containing the values and add all values to a List final Set<String> valueColumns = columnMapping.getValue(); final List<Object> attrValues = new ArrayList<>(valueColumns.size()); for (final String valueColumn : valueColumns) { final Object attrValue = queryResult.get(valueColumn); if (attrValue == null && !queryResult.containsKey(valueColumn)) { throw new BadSqlGrammarException( "No attribute value column named '" + valueColumn + "' exists in result set", this.getQueryTemplate(), null); } attrValues.add(attrValue); } //Add the name/values to the attributes Map MultivaluedPersonAttributeUtils.addResult(attributes, attrName, attrValues); } } //Convert the builder structure into a List of IPersons final List<IPersonAttributes> people = new ArrayList<>(peopleAttributesBuilder.size()); for (final Map.Entry<String, Map<String, List<Object>>> mappedAttributesEntry : peopleAttributesBuilder .entrySet()) { final String userName = mappedAttributesEntry.getKey(); final Map<String, List<Object>> attributes = mappedAttributesEntry.getValue(); // PERSONDIR-89, PERSONDIR-91 Should this be CaseInsensitiveNamedPersonImpl like SingleRowJdbcPersonAttribute? final IPersonAttributes person = new NamedPersonImpl(userName, attributes); people.add(person); } return people; } }