View Javadoc

1   /* Copyright 2004 The JA-SIG Collaborative.  All rights reserved.
2   *  See license distributed with this file and
3   *  available online at http://www.uportal.org/license.html
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         //Iterate through the List of results, each item should be equal to a row from the ResultSet
152         for (final Iterator rowItr = queryResults.iterator(); rowItr.hasNext();) {
153             final Map rowResult = (Map)rowItr.next();
154             
155             //Iterate through the name/value(s) pairs for each row Map
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                 //Get the Set of portal user attribute names the value(s) should be stored under.
161                 final Set portalAttrNames = (Set)this.attributeNameMappings.get(srcAttrName);
162                 
163                 //If no portal user attribute names are mapped add the value(s) with the attribute name from the result set
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                     //For each mapped portlet attribute name store the value(s)
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      * @see org.jasig.portal.services.persondir.support.IPersonAttributeDao#getPossibleUserAttributeNames()
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 }