Index: src/main/java/net/shibboleth/idp/attribute/resolver/dc/rdbms/impl/NameValuePairMappingStrategy.java =================================================================== --- src/main/java/net/shibboleth/idp/attribute/resolver/dc/rdbms/impl/NameValuePairMappingStrategy.java (revision 0) +++ src/main/java/net/shibboleth/idp/attribute/resolver/dc/rdbms/impl/NameValuePairMappingStrategy.java (revision 0) @@ -0,0 +1,119 @@ +/* + * Insert license here + */ + +package net.shibboleth.idp.attribute.resolver.dc.rdbms.impl; + +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import net.shibboleth.idp.attribute.IdPAttribute; +import net.shibboleth.idp.attribute.IdPAttributeValue; +import net.shibboleth.idp.attribute.StringAttributeValue; +import net.shibboleth.idp.attribute.resolver.MultipleResultAnErrorResolutionException; +import net.shibboleth.idp.attribute.resolver.NoResultAnErrorResolutionException; +import net.shibboleth.idp.attribute.resolver.ResolutionException; +import net.shibboleth.idp.attribute.resolver.dc.AbstractMappingStrategy; +import net.shibboleth.utilities.java.support.logic.Constraint; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A simple {@link ResultMappingStrategy} that assumes each row in the result set should be mapped as a name-value pair + * and that all values are strings. + */ +public class NameValuePairMappingStrategy extends AbstractMappingStrategy implements ResultMappingStrategy { + + /** Class logger. */ + @Nonnull private final Logger log = LoggerFactory.getLogger(NameValuePairMappingStrategy.class); + + /** + * This column or alias will be interpreted as an attribute name. + * Note DB2 doesn't like leading underscores so you'll have to double-quote the alias. + * e.g. SELECT name AS "_NVP_NAME_" + */ + public final static String NAME_COLUMN = "_NVP_NAME_"; + + /** This column or alias will be interpreted as an attribute value. See notes for NAME_COLUMN. */ + public final static String VALUE_COLUMN = "_NVP_VALUE_"; + +// Checkstyle: CyclomaticComplexity OFF + /** {@inheritDoc} */ + @Override @Nullable public Map map(@Nonnull final ResultSet results) + throws ResolutionException { + Constraint.isNotNull(results, "Result set can not be null"); + + try { + if (!results.next()) { + log.debug("Result set did not contain any rows, nothing to map"); + if (isNoResultAnError()) { + throw new NoResultAnErrorResolutionException("No rows returned from query"); + } + return null; + } + + final ResultSetMetaData resultMetadata = results.getMetaData(); + + final Map attributes = new HashMap<>(resultMetadata.getColumnCount()); + + final Map aliases = getResultRenamingMap(); + + int rowCount = 0; + + do { + + if (++rowCount > 1 && isMultipleResultsAnError()) { + throw new MultipleResultAnErrorResolutionException("Multiple rows returned from query"); + } + + String name = results.getString(NAME_COLUMN).trim(); + String value = results.getString(VALUE_COLUMN); + + if (name != null && !name.isEmpty()) { + log.debug("Retrieved potential attribute with name {}", name); + + IdPAttribute attribute = attributes.get(name); + if (attribute == null) { + attribute = new IdPAttribute(name); + attributes.put(name, attribute); + log.trace("Creating new attribute object"); + } + + if (attribute.getValues().isEmpty()) { + attribute.setValues(Collections.singletonList(StringAttributeValue.valueOf(value))); + log.trace("Setting value {} for attribute", value, name); + } else { + final List> values = new ArrayList<>(attribute.getValues()); + values.add(StringAttributeValue.valueOf(value)); + attribute.setValues(values); + log.trace("Adding value {} to attribute {}", value, name); + } + + } else { + log.debug("Skipping unmappable row"); + } + + } while (results.next()); + + if (attributes.isEmpty()) { + return null; + } else { + return attributes; + } + } catch (final SQLException e) { + throw new ResolutionException("Error reading data from result set", e); + } + } +// Checkstyle: CyclomaticComplexity ON + +} \ No newline at end of file Index: src/test/java/net/shibboleth/idp/attribute/resolver/dc/rdbms/impl/RDBMSDataConnectorTest.java =================================================================== --- src/test/java/net/shibboleth/idp/attribute/resolver/dc/rdbms/impl/RDBMSDataConnectorTest.java (revision 8103) +++ src/test/java/net/shibboleth/idp/attribute/resolver/dc/rdbms/impl/RDBMSDataConnectorTest.java (working copy) @@ -63,6 +63,8 @@ private static final String GROUP_QUERY = "SELECT name FROM groups WHERE userid='%s'"; + private static final String NVP_QUERY = "SELECT attr_name AS _NVP_NAME_, attr_value AS _NVP_VALUE_ FROM nvp_attributes WHERE userid='%s'"; + private DataSource datasource; /** @@ -384,4 +386,17 @@ Assert.assertTrue(attrs.get("MAIL").getValues().contains(new StringAttributeValue(" phil.principal@shibboleth.net "))); } + @Test public void resolveNVPAttributes() throws ComponentInitializationException, ResolutionException { + RDBMSDataConnector connector = createUserRdbmsDataConnector(new FormatExecutableStatementBuilder(NVP_QUERY), new NameValuePairMappingStrategy()); + connector.initialize(); + + AttributeResolutionContext context = + TestSources.createResolutionContext("PHILIP_THE_PRINCIPAL", TestSources.IDP_ENTITY_ID, + TestSources.SP_ENTITY_ID); + Map attrs = connector.resolve(context); + Assert.assertTrue(attrs.size() == 2); + Assert.assertTrue(attrs.get("department").getValues().size() == 2); + Assert.assertTrue(attrs.get("title").getValues().size() == 1); + } + } \ No newline at end of file Index: src/test/resources/data/net/shibboleth/idp/attribute/resolver/impl/dc/rdbms/RdbmsData.sql =================================================================== --- src/test/resources/data/net/shibboleth/idp/attribute/resolver/impl/dc/rdbms/RdbmsData.sql (revision 8103) +++ src/test/resources/data/net/shibboleth/idp/attribute/resolver/impl/dc/rdbms/RdbmsData.sql (working copy) @@ -65,4 +65,28 @@ INSERT INTO groups (userid, name) values ( 'PETER_THE_PRINCIPAL', - 'group2'); \ No newline at end of file + 'group2'); + +INSERT INTO nvp_attributes (userid, attr_name, attr_value) + values ( + 'PHILIP_THE_PRINCIPAL', + 'department', + '123' + ); + +INSERT INTO nvp_attributes (userid, attr_name, attr_value) + values ( + 'PHILIP_THE_PRINCIPAL', + 'department', + '456' + ); + +INSERT INTO nvp_attributes (userid, attr_name, attr_value) + values ( + 'PHILIP_THE_PRINCIPAL', + 'title', + 'Principal Analyst' + ); + + + \ No newline at end of file Index: src/test/resources/data/net/shibboleth/idp/attribute/resolver/impl/dc/rdbms/RdbmsStore.sql =================================================================== --- src/test/resources/data/net/shibboleth/idp/attribute/resolver/impl/dc/rdbms/RdbmsStore.sql (revision 8103) +++ src/test/resources/data/net/shibboleth/idp/attribute/resolver/impl/dc/rdbms/RdbmsStore.sql (working copy) @@ -10,4 +10,12 @@ CREATE TABLE groups ( userid VARCHAR(50) NOT NULL, name VARCHAR(50) NOT NULL - ); \ No newline at end of file + ); + +CREATE TABLE nvp_attributes ( + userid VARCHAR(50) NOT NULL, + attr_name VARCHAR(50) NOT NULL, + attr_value VARCHAR(50) NOT NULL +); + +