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.util.Collections;
009: import java.util.HashMap;
010: import java.util.HashSet;
011: import java.util.Iterator;
012: import java.util.List;
013: import java.util.Map;
014: import java.util.Set;
015:
016: import org.jasig.portal.services.persondir.IPersonAttributeDao;
017: import org.jasig.portal.services.persondir.support.merger.IAttributeMerger;
018: import org.jasig.portal.services.persondir.support.merger.ReplacingAttributeAdder;
019:
020: /**
021: * This {@link org.jasig.portal.services.persondir.IPersonAttributeDao}
022: * implementation iterates through an ordered {@link List} of
023: * {@link org.jasig.portal.services.persondir.IPersonAttributeDao} impls
024: * when getting user attributes.
025: * <br>
026: * The first DAO is queried using the seed {@link Map} passed to this class. The results
027: * of the query are merged into a general result map. After the first DAO this general
028: * result map used as the query seed for each DAO and each DAOs results are merged into it.
029: * <br>
030: * This behavior allows a DAO lower on the list to rely on attributes returned by a DAO
031: * higher on the list.
032: * <br>
033: * The default merger for the general result set is {@link ReplacingAttributeAdder}.
034: * <br>
035: * Note that most DAOs expect a Map of String->String. Some of the DAOs return a Map of
036: * String->Object or String->List. This may cause problems in the DAO if the key for an
037: * attribute with a non String value matches a key needed by the DAO for the query it is
038: * running.
039: * <br>
040: * It is <u>highly</u> recomended that the first DAO on the list for this class is
041: * the {@link org.jasig.portal.services.persondir.support.EchoPersonAttributeDaoImpl}
042: * to ensure the seed gets placed into the general result map.
043: *
044: * @author Eric Dalquist <a href="mailto:edalquist@unicon.net">edalquist@unicon.net</a>
045: * @version $Revision: 35515 $ $Date: 2005-04-06 08:31:40 -0700 (Wed, 06 Apr 2005) $
046: * @since uPortal 2.5
047: */
048: public class CascadingPersonAttributeDao extends
049: AbstractDefaultQueryPersonAttributeDao {
050:
051: /**
052: * A List of child IPersonAttributeDao instances which we will poll in order.
053: */
054: private List personAttributeDaos;
055:
056: /**
057: * Strategy for merging together the results from successive PersonAttributeDaos.
058: */
059: private IAttributeMerger attrMerger = new ReplacingAttributeAdder();
060:
061: /**
062: * True if we should catch, log, and ignore Throwables propogated by
063: * individual DAOs.
064: */
065: private boolean recoverExceptions = true;
066:
067: /**
068: * Iterates through the configured {@link List} of {@link IPersonAttributeDao}
069: * instances. The results from each DAO are merged into the result {@link Map}
070: * by the configured {@link IAttributeMerger}.
071: *
072: * @see org.jasig.portal.services.persondir.IPersonAttributeDao#getUserAttributes(java.util.Map)
073: */
074: public Map getUserAttributes(final Map seed) {
075: //Ensure the arguements and state are valid
076: if (seed == null)
077: throw new IllegalArgumentException(
078: "The query seed Map cannot be null.");
079:
080: if (this .personAttributeDaos == null)
081: throw new IllegalStateException(
082: "No IPersonAttributeDaos have been specified.");
083:
084: //Initialize null, so that if none of the sub-DAOs find the user null is returned appropriately
085: Map resultAttributes = null;
086:
087: //Denotes that this is the first time we are running a query and the seed should be used
088: boolean isFirstQuery = true;
089:
090: //Iterate through the configured IPersonAttributeDaos, querying each.
091: for (final Iterator iter = this .personAttributeDaos.iterator(); iter
092: .hasNext();) {
093: final IPersonAttributeDao currentlyConsidering = (IPersonAttributeDao) iter
094: .next();
095:
096: Map currentAttributes = new HashMap();
097: try {
098: if (isFirstQuery) {
099: currentAttributes = currentlyConsidering
100: .getUserAttributes(seed);
101: isFirstQuery = false; //Ran a query successfully, stop using the seed
102: } else
103: currentAttributes = currentlyConsidering
104: .getUserAttributes(resultAttributes);
105: } catch (final RuntimeException rte) {
106: final String msg = "Exception thrown by DAO: "
107: + currentlyConsidering;
108:
109: if (this .recoverExceptions) {
110: log.warn("Recovering From " + msg, rte);
111: } else {
112: log.error(msg, rte);
113: throw rte;
114: }
115: }
116:
117: if (resultAttributes == null) {
118: //If this is the first valid result set just use it.
119: resultAttributes = currentAttributes;
120: } else if (currentAttributes != null) {
121: //Perform the appropriate attribute attrMerger
122: resultAttributes = this .attrMerger.mergeAttributes(
123: resultAttributes, currentAttributes);
124: }
125: }
126:
127: return resultAttributes;
128: }
129:
130: /**
131: * This implementation is not always correct.
132: * It handles the basic case where the Set of attributes returned by this
133: * implementation is the union of the attributes declared by all of the
134: * underlying implementations to be merged. Of course, an IAttributeMerger
135: * might provide for a merging policy such that the attributes resulting from
136: * invoking this IPersonAttributeDao implementation are not the union
137: * of the attributes declared by the underlying PersonAttributeDaos.
138: *
139: * @see org.jasig.portal.services.persondir.IPersonAttributeDao#getPossibleUserAttributeNames()
140: */
141: public Set getPossibleUserAttributeNames() {
142: final Set attrNames = new HashSet();
143:
144: for (final Iterator iter = this .personAttributeDaos.iterator(); iter
145: .hasNext();) {
146: final IPersonAttributeDao currentDao = (IPersonAttributeDao) iter
147: .next();
148:
149: Set currentDaoAttrNames = null;
150: try {
151: currentDaoAttrNames = currentDao
152: .getPossibleUserAttributeNames();
153: } catch (final RuntimeException rte) {
154: final String msg = "Exception thrown by DAO: "
155: + currentDao;
156:
157: if (this .recoverExceptions) {
158: log.warn(msg
159: + ", ignoring this DAO and continuing.",
160: rte);
161: } else {
162: log.error(msg + ", failing.", rte);
163: throw rte;
164: }
165: }
166:
167: if (currentDaoAttrNames != null)
168: attrNames.addAll(currentDaoAttrNames);
169: }
170:
171: return Collections.unmodifiableSet(attrNames);
172: }
173:
174: /**
175: * Get the strategy whereby we accumulate attributes.
176: *
177: * @return Returns the attrMerger.
178: */
179: public IAttributeMerger getMerger() {
180: return this .attrMerger;
181: }
182:
183: /**
184: * Set the strategy whereby we accumulate attributes from the results of
185: * polling our delegates.
186: *
187: * @param merger The attrMerger to set.
188: * @throws IllegalArgumentException If merger is <code>null</code>.
189: */
190: public void setMerger(final IAttributeMerger merger) {
191: if (merger == null)
192: throw new IllegalArgumentException(
193: "The merger cannot be null");
194:
195: this .attrMerger = merger;
196: }
197:
198: /**
199: * Get an unmodifiable {@link List} of delegates which we will poll for attributes.
200: *
201: * @return Returns the personAttributeDaos.
202: */
203: public List getPersonAttributeDaos() {
204: return this .personAttributeDaos;
205: }
206:
207: /**
208: * Set the {@link List} of delegates which we will poll for attributes.
209: *
210: * @param daos The personAttributeDaos to set.
211: * @throws IllegalArgumentException If daos is <code>null</code>.
212: */
213: public void setPersonAttributeDaos(final List daos) {
214: if (daos == null)
215: throw new IllegalArgumentException(
216: "The dao list cannot be null");
217:
218: this .personAttributeDaos = Collections.unmodifiableList(daos);
219: }
220:
221: /**
222: * True if this class will catch exceptions thrown by its delegate DAOs
223: * and fail to propogate them. False if this class will stop on failure.
224: *
225: * @return true if will recover exceptions, false otherwise
226: */
227: public boolean isRecoverExceptions() {
228: return this .recoverExceptions;
229: }
230:
231: /**
232: * Set to true if you would like this class to swallow RuntimeExceptions
233: * thrown by its delegates. This allows it to recover if a particular attribute
234: * source fails, still considering previous and subsequent sources.
235: * Set to false if you would like this class to fail hard upon any Throwable
236: * thrown by its children. This is desirable in cases where your Portal will not
237: * function without attributes from all of its sources.
238: *
239: * @param recover whether you would like exceptions recovered internally
240: */
241: public void setRecoverExceptions(boolean recover) {
242: this.recoverExceptions = recover;
243: }
244: }
|