1
2
3
4
5
6 package org.jasig.services.persondir.support.jdbc;
7
8 import java.util.Collection;
9 import java.util.Collections;
10 import java.util.HashMap;
11 import java.util.HashSet;
12 import java.util.Iterator;
13 import java.util.List;
14 import java.util.Map;
15 import java.util.Set;
16
17 import javax.sql.DataSource;
18
19 import org.jasig.services.persondir.support.MultivaluedPersonAttributeUtils;
20 import org.springframework.jdbc.object.MappingSqlQuery;
21
22 /***
23 * An {@link org.jasig.portal.services.persondir.IPersonAttributeDao}
24 * implementation that maps attribute names and values from name and value column
25 * pairs. This is usefull if user attributes are stored in a table like:<br>
26 * <table border="1">
27 * <tr>
28 * <th>USER_NM</th>
29 * <th>ATTR_NM</th>
30 * <th>ATTR_VL</th>
31 * </tr>
32 * <tr>
33 * <td>jstudent</td>
34 * <td>name.given</td>
35 * <td>joe</td>
36 * </tr>
37 * <tr>
38 * <td>jstudent</td>
39 * <td>name.family</td>
40 * <td>student</td>
41 * </tr>
42 * <tr>
43 * <td>badvisor</td>
44 * <td>name.given</td>
45 * <td>bob</td>
46 * </tr>
47 * <tr>
48 * <td>badvisor</td>
49 * <td>name.family</td>
50 * <td>advisor</td>
51 * </tr>
52 * </table>
53 *
54 * <br>
55 *
56 * This class expects 1 to N row results for a query, with each row containing 1 to N name
57 * value attribute mappings. This contrasts {@link org.jasig.portal.services.persondir.support.jdbc.SingleRowJdbcPersonAttributeDao}
58 * which expects a single row result for a user query. <br>
59 *
60 * <br>
61 * <br>
62 * Configuration:
63 * <table border="1">
64 * <tr>
65 * <th align="left">Property</th>
66 * <th align="left">Description</th>
67 * <th align="left">Required</th>
68 * <th align="left">Default</th>
69 * </tr>
70 * <tr>
71 * <td align="right" valign="top">attributeNameMappings</td>
72 * <td>
73 * Maps attribute names as defined in the database to attribute names to be exposed
74 * to the client code. The keys of the Map must be Strings, the values may be
75 * <code>null</code>, String or a Set of Strings. The keySet of this Map is returned
76 * as the possibleUserAttributeNames property. If an attribute name is not in
77 * the map the attribute name will be used in as the returned attribute name.
78 * </td>
79 * <td valign="top">No</td>
80 * <td valign="top">{@link java.util.Collections#EMPTY_MAP}</td>
81 * </tr>
82 * <tr>
83 * <td align="right" valign="top">nameValueColumnMappings</td>
84 * <td>
85 * The {@link Map} of columns from a name column to value columns. Keys are Strings,
86 * Values are Strings or {@link java.util.List} of Strings
87 * </td>
88 * <td valign="top">Yes</td>
89 * <td valign="top">null</td>
90 * </tr>
91 * </table>
92 *
93 * @author andrew.petro@yale.edu
94 * @author Eric Dalquist <a href="mailto:edalquist@unicon.net">edalquist@unicon.net</a>
95 * @version $Revision: 2892 $ $Date: 2006-12-19 13:25:03 -0600 (Tue, 19 Dec 2006) $
96 * @since uPortal 2.5
97 */
98 public class MultiRowJdbcPersonAttributeDao extends AbstractJdbcPersonAttributeDao {
99 /***
100 * {@link Map} from stored names to attribute names.
101 * Keys are Strings, Values are null, Strings or List of Strings
102 */
103 private Map attributeNameMappings = Collections.EMPTY_MAP;
104
105 /***
106 * {@link Map} of columns from a name column to value columns.
107 * Keys are Strings, Values are Strings or Lost of Strings
108 */
109 private Map nameValueColumnMappings = null;
110
111 /***
112 * {@link Set} of attributes that may be provided for a user.
113 */
114 private Set userAttributes = Collections.EMPTY_SET;
115
116 /***
117 * The {@link MappingSqlQuery} to use to get attributes.
118 */
119 private final MultiRowPersonAttributeMappingQuery query;
120
121 /***
122 * Creates a new MultiRowJdbcPersonAttributeDao specifying the DataSource and SQL to use.
123 *
124 * @param ds The DataSource to get connections from for executing queries, may not be null.
125 * @param attrList Sets the query attribute list to pass to {@link AbstractJdbcPersonAttributeDao#setQueryAttributes(List)} and {@link MultiRowPersonAttributeMappingQuery#MultiRowPersonAttributeMappingQuery(DataSource, String, List, MultiRowJdbcPersonAttributeDao)}
126 * @param sql The SQL to execute for user attributes, may not be null.
127 */
128 public MultiRowJdbcPersonAttributeDao(DataSource ds, List attrList, String sql) {
129 if (ds == null) {
130 throw new IllegalArgumentException("DataSource can not be null");
131 }
132 if (sql == null) {
133 throw new IllegalArgumentException("The sql can not be null");
134 }
135
136 this.setQueryAttributes(attrList);
137 final List queryAttributes = this.getQueryAttributes();
138 this.query = new MultiRowPersonAttributeMappingQuery(ds, sql, queryAttributes, this);
139 }
140
141
142 /***
143 * Returned {@link Map} will have values of {@link String} or a
144 * {@link List} of {@link String}.
145 *
146 * @see org.jasig.portal.services.persondir.IPersonAttributeDao#getUserAttributes(java.util.Map)
147 */
148 public Map parseAttributeMapFromResults(final List queryResults) {
149 final Map results = new HashMap();
150
151
152 for (final Iterator rowItr = queryResults.iterator(); rowItr.hasNext();) {
153 final Map rowResult = (Map)rowItr.next();
154
155
156 for (final Iterator resultItr = rowResult.entrySet().iterator(); resultItr.hasNext();) {
157 final Map.Entry entry = (Map.Entry)resultItr.next();
158 final String srcAttrName = (String)entry.getKey();
159
160
161 final Set portalAttrNames = (Set)this.attributeNameMappings.get(srcAttrName);
162
163
164 if (portalAttrNames == null) {
165 if (this.logger.isDebugEnabled()) {
166 this.logger.debug("Adding un-mapped attribute '" + srcAttrName + "'");
167 }
168
169 MultivaluedPersonAttributeUtils.addResult(results, srcAttrName, entry.getValue());
170 }
171 else {
172
173 for (final Iterator upAttrNameItr = portalAttrNames.iterator(); upAttrNameItr.hasNext();) {
174 final String portalAttrName = (String)upAttrNameItr.next();
175
176 if (this.logger.isDebugEnabled()) {
177 this.logger.debug("Adding mapped attribute '" + portalAttrName + "' for source attribute '" + srcAttrName + "'");
178 }
179
180 MultivaluedPersonAttributeUtils.addResult(results, portalAttrName, entry.getValue());
181 }
182 }
183 }
184 }
185
186 if (this.logger.isInfoEnabled()) {
187 this.logger.info("Mapped " + results.size() + " portal attributes from " + queryResults.size() + " source attributes");
188 }
189
190 return results;
191 }
192
193 /***
194 * @see org.jasig.portal.services.persondir.support.jdbc.AbstractJdbcPersonAttributeDao#getAttributeQuery()
195 */
196 protected AbstractPersonAttributeMappingQuery getAttributeQuery() {
197 return this.query;
198 }
199
200
201
202
203 public Set getPossibleUserAttributeNames() {
204 return this.userAttributes;
205 }
206
207 /***
208 * Get the Map from non-null String column names to Sets of non-null Strings
209 * representing the names of the uPortal attributes to be initialized from
210 * the specified column.
211 * @return Returns the attributeMappings mapping.
212 */
213 public Map getAttributeNameMappings() {
214 return this.attributeNameMappings;
215 }
216
217 /***
218 * The passed {@link Map} must have keys of type {@link String} and values
219 * of type {@link String} or a {@link Set} of {@link String}.
220 *
221 * @param attributeNameMap {@link Map} from column names to attribute names, may not be null.
222 * @throws IllegalArgumentException If the {@link Map} doesn't follow the rules stated above.
223 * @see MultivaluedPersonAttributeUtils#parseAttributeToAttributeMapping(Map)
224 */
225 public void setAttributeNameMappings(final Map attributeNameMap) {
226 if (attributeNameMap == null) {
227 throw new IllegalArgumentException("columnsToAttributesMap may not be null");
228 }
229
230 this.attributeNameMappings = MultivaluedPersonAttributeUtils.parseAttributeToAttributeMapping(attributeNameMap);
231
232 if (this.attributeNameMappings.containsKey("")) {
233 throw new IllegalArgumentException("The map from attribute names to attributes must not have any empty keys.");
234 }
235
236 final Collection userAttributeCol = MultivaluedPersonAttributeUtils.flattenCollection(this.attributeNameMappings.values());
237
238 this.userAttributes = Collections.unmodifiableSet(new HashSet(userAttributeCol));
239 }
240
241
242 /***
243 * @return The Map of name column to value column(s).
244 */
245 public Map getNameValueColumnMappings() {
246 return this.nameValueColumnMappings;
247 }
248
249 /***
250 * The {@link Map} of columns from a name column to value columns. Keys are Strings,
251 * Values are Strings or {@link java.util.List} of Strings.
252 *
253 * @param nameValueColumnMap The Map of name column to value column(s).
254 */
255 public void setNameValueColumnMappings(final Map nameValueColumnMap) {
256 if (nameValueColumnMap == null) {
257 this.nameValueColumnMappings = null;
258 }
259 else {
260 final Map mappings = MultivaluedPersonAttributeUtils.parseAttributeToAttributeMapping(nameValueColumnMap);
261
262 if (mappings.containsValue(null)) {
263 throw new IllegalArgumentException("nameValueColumnMap may not have null values");
264 }
265
266 this.nameValueColumnMappings = mappings;
267 }
268 }
269 }