001: /* Copyright 2004 The JA-SIG Collaborative. All rights reserved.
002: * See license distributed with this file and
003: * available online at http://www.uportal.org/license.html
004: */
005:
006: package org.jasig.portal.services.persondir.support;
007:
008: import java.sql.ResultSet;
009: import java.sql.SQLException;
010: import java.sql.Types;
011: import java.util.ArrayList;
012: import java.util.Collection;
013: import java.util.Collections;
014: import java.util.HashMap;
015: import java.util.HashSet;
016: import java.util.Iterator;
017: import java.util.List;
018: import java.util.Map;
019: import java.util.Set;
020:
021: import javax.sql.DataSource;
022:
023: import org.springframework.dao.support.DataAccessUtils;
024: import org.springframework.jdbc.core.SqlParameter;
025: import org.springframework.jdbc.object.MappingSqlQuery;
026:
027: /**
028: * An {@link org.jasig.portal.services.persondir.IPersonAttributeDao}
029: * implementation that maps from column names in the result of a SQL query
030: * to attribute names. <br>
031: * You must set a Map from column names to attribute names and only column names
032: * appearing as keys in that map will be used.
033: * <br>
034: *
035: * @author andrew.petro@yale.edu
036: * @author Eric Dalquist <a href="mailto:edalquist@unicon.net">edalquist@unicon.net</a>
037: * @version $Revision: 35595 $ $Date: 2005-04-17 14:26:20 -0700 (Sun, 17 Apr 2005) $
038: * @since uPortal 2.5
039: */
040: public class JdbcPersonAttributeDaoImpl extends
041: AbstractDefaultQueryPersonAttributeDao {
042: /**
043: * {@link Map} from column names to attribute names.
044: */
045: private Map attributeMappings = Collections.EMPTY_MAP;
046:
047: /**
048: * {@link Set} of attributes that may be provided for a user.
049: */
050: private Set userAttributes = Collections.EMPTY_SET;
051:
052: /**
053: * {@link List} of attributes to use in the query.
054: */
055: private List queryAttributes = Collections.EMPTY_LIST;
056:
057: /**
058: * The {@link MappingSqlQuery} to use to get attributes.
059: */
060: private PersonAttributeMappingQuery query;
061:
062: /**
063: * Create the DAO, configured with the needed query information.
064: *
065: * @param ds The {@link DataSource} to run the queries against.
066: * @param attrList The list of arguments for the query.
067: * @param sql The SQL query to run.
068: */
069: public JdbcPersonAttributeDaoImpl(final DataSource ds,
070: final List attrList, final String sql) {
071: if (super .log.isTraceEnabled()) {
072: log.trace("entering JdbcPersonAttributeDaoImpl(" + ds
073: + ", " + attrList + ", " + sql + ")");
074: }
075: if (attrList == null)
076: throw new IllegalArgumentException(
077: "attrList cannot be null");
078:
079: //Defensive copy of the query attribute list
080: List defensiveCopy = new ArrayList(attrList);
081: this .queryAttributes = Collections
082: .unmodifiableList(defensiveCopy);
083:
084: this .query = new PersonAttributeMappingQuery(ds, sql);
085: if (log.isTraceEnabled()) {
086: log.trace("Constructed " + this );
087: }
088: }
089:
090: /**
091: * Returned {@link Map} will have values of {@link String} or a
092: * {@link List} of {@link String}.
093: *
094: * @see org.jasig.portal.services.persondir.IPersonAttributeDao#getUserAttributes(java.util.Map)
095: */
096: public Map getUserAttributes(final Map seed) {
097: if (seed == null)
098: throw new IllegalArgumentException(
099: "The query seed Map cannot be null.");
100:
101: //Ensure the data needed to run the query is avalable
102: if (!((queryAttributes != null && seed.keySet().containsAll(
103: queryAttributes)) || (queryAttributes == null && seed
104: .containsKey(this .getDefaultAttributeName())))) {
105: return null;
106: }
107:
108: //Can't just to a toArray here since the order of the keys in the Map
109: //may not match the order of the keys in the List and it is important to
110: //the query.
111: Object[] args = new Object[this .queryAttributes.size()];
112:
113: for (int index = 0; index < args.length; index++) {
114: final String attrName = (String) this .queryAttributes
115: .get(index);
116: args[index] = seed.get(attrName);
117: }
118:
119: final List queryResults = this .query.execute(args);
120: final Map uniqueResult = (Map) DataAccessUtils
121: .uniqueResult(queryResults);
122:
123: //If it's null no user was found, correct behavior is to return null
124: return uniqueResult;
125: }
126:
127: /*
128: * @see org.jasig.portal.services.persondir.support.IPersonAttributeDao#getPossibleUserAttributeNames()
129: */
130: public Set getPossibleUserAttributeNames() {
131: return this .userAttributes;
132: }
133:
134: /**
135: * Get the Map from non-null String column names to Sets of non-null Strings
136: * representing the names of the uPortal attributes to be initialized from
137: * the specified column.
138: * @return Returns the attributeMappings mapping.
139: */
140: public Map getColumnsToAttributes() {
141: return this .attributeMappings;
142: }
143:
144: /**
145: * Set the {@link Map} to use for mapping from a column name to a attribute
146: * name or {@link Set} of attribute names. Column names that are specified
147: * but have null mappings will use the column name for the attribute name.
148: * Column names that are not specified as keys in this {@link Map} will be
149: * ignored.
150: * <br>
151: * The passed {@link Map} must have keys of type {@link String} and values
152: * of type {@link String} or a {@link Set} of {@link String}.
153: *
154: * @param columnsToAttributesMap {@link Map} from column names to attribute names.
155: * @throws IllegalArgumentException If the {@link Map} doesn't follow the rules stated above.
156: * @see MultivaluedPersonAttributeUtils#parseAttributeToAttributeMapping(Map)
157: */
158: public void setColumnsToAttributes(final Map columnsToAttributesMap) {
159: if (columnsToAttributesMap != null) {
160: this .attributeMappings = MultivaluedPersonAttributeUtils
161: .parseAttributeToAttributeMapping(columnsToAttributesMap);
162: final Collection userAttributeCol = MultivaluedPersonAttributeUtils
163: .flattenCollection(this .attributeMappings.values());
164:
165: this .userAttributes = Collections
166: .unmodifiableSet(new HashSet(userAttributeCol));
167: } else {
168: this .attributeMappings = null;
169: this .userAttributes = Collections.EMPTY_SET;
170: }
171: }
172:
173: public String toString() {
174: StringBuffer sb = new StringBuffer();
175: sb.append("JdbcPersonAttributeDaoImpl ");
176: sb.append("query=").append(this .query);
177: sb.append(" queryAttributes=").append(this .queryAttributes);
178: sb.append(" attributeMappings=").append(this .attributeMappings);
179: return sb.toString();
180: }
181:
182: /**
183: * An object which will execute a SQL query with the expectation
184: * of yielding a ResultSet with zero or one rows, which it maps
185: * to null or to a Map from uPortal attribute names to values.
186: */
187: private class PersonAttributeMappingQuery extends MappingSqlQuery {
188: /**
189: * Instantiate the query, providing a DataSource against which the query
190: * will run and the SQL representing the query, which should take exactly
191: * one parameter: the unique ID of the user.
192: *
193: * @param ds The data source to use for running the query against.
194: * @param sql The SQL to run against the data source.
195: */
196: public PersonAttributeMappingQuery(final DataSource ds,
197: final String sql) {
198: super (ds, sql);
199:
200: //Assume to parameters needed if the query attribute list is null
201: if (queryAttributes != null) {
202: //Configures the SQL parameters, everything is assumed to be VARCHAR
203: for (final Iterator attrNames = queryAttributes
204: .iterator(); attrNames.hasNext();) {
205: final String attrName = (String) attrNames.next();
206: this .declareParameter(new SqlParameter(attrName,
207: Types.VARCHAR));
208: }
209: }
210:
211: //One time compilation of the query
212: this .compile();
213: }
214:
215: /**
216: * How attribute name mapping works:
217: * If the column is mapped use the mapped name(s)<br>
218: * If the column is listed and not mapped use the column name<br>
219: *
220: * @see org.springframework.jdbc.object.MappingSqlQuery#mapRow(java.sql.ResultSet, int)
221: */
222: protected Object mapRow(final ResultSet rs, final int rowNum)
223: throws SQLException {
224: final Map rowResults = new HashMap();
225:
226: //Iterates through any mapped columns that did appear in the column list from the result set
227: //this will probably throw an exception every time since the column won't be found
228: for (final Iterator columnNameItr = attributeMappings
229: .keySet().iterator(); columnNameItr.hasNext();) {
230: final String columnName = (String) columnNameItr.next();
231:
232: this .addMappedAttributes(rs, columnName, rowResults);
233: }
234:
235: return rowResults;
236: }
237:
238: /**
239: * Tries to get the attributes specified for the column, determin the
240: * mapping for the column and add it to the rowResults {@link Map}.
241: *
242: * @param rs The {@link ResultSet} to get the attribute value from.
243: * @param columnName The name of the column to get the attribute value from.
244: * @param rowResults The {@link Map} to add the mapped attribute to.
245: * @throws SQLException If there is a problem retrieving the value from the {@link ResultSet}.
246: */
247: private void addMappedAttributes(final ResultSet rs,
248: final String columnName, final Map rowResults)
249: throws SQLException {
250: if (columnName == null || columnName.length() <= 0)
251: throw new IllegalArgumentException(
252: "columnName cannot be null and must have length >= 0");
253:
254: String attributeValue = null;
255:
256: //Get the database value
257: try {
258: attributeValue = rs.getString(columnName);
259: } catch (SQLException sqle) {
260: super .logger
261: .error("Was unable to read attribute for column ["
262: + columnName + "]");
263: throw sqle;
264: }
265:
266: //See if the column is mapped
267: Set attributeNames = (Set) attributeMappings
268: .get(columnName);
269:
270: //No mapping was found, just use the column name
271: if (attributeNames == null)
272: attributeNames = Collections.singleton(columnName);
273:
274: //Run through the mapped attribute names
275: for (final Iterator attrNameItr = attributeNames.iterator(); attrNameItr
276: .hasNext();) {
277: final String attributeName = (String) attrNameItr
278: .next();
279:
280: MultivaluedPersonAttributeUtils.addResult(rowResults,
281: attributeName, attributeValue);
282: }
283: }
284:
285: public String toString() {
286: StringBuffer sb = new StringBuffer();
287: sb.append(this .getClass().getName());
288: sb.append(" SQL=[").append(super .getSql()).append("]");
289: return sb.toString();
290: }
291: }
292: }
|