001: /* Copyright 2005 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: package org.jasig.portal.services.persondir.support;
006:
007: import java.io.Serializable;
008: import java.util.HashMap;
009: import java.util.Iterator;
010: import java.util.Map;
011: import java.util.Set;
012:
013: import org.jasig.portal.services.persondir.IPersonAttributeDao;
014: import org.jasig.portal.utils.cache.CacheFactory;
015: import org.jasig.portal.utils.cache.CacheFactoryLocator;
016:
017: /**
018: * A configurable caching implementation of {@link IPersonAttributeDao}
019: * which caches results from a wrapped IPersonAttributeDao.
020: * <br>
021: * <br>
022: * Configuration:
023: * <table border="1">
024: * <tr>
025: * <th align="left">Property</th>
026: * <th align="left">Description</th>
027: * <th align="left">Required</th>
028: * <th align="left">Default</th>
029: * </tr>
030: * <tr>
031: * <td align="right" valign="top">cachedPersonAttributesDao</td>
032: * <td>
033: * The {@link org.jasig.portal.services.persondir.IPersonAttributeDao} to delegate
034: * queries to on cache misses.
035: * </td>
036: * <td valign="top">Yes</td>
037: * <td valign="top">null</td>
038: * </tr>
039: * <tr>
040: * <td align="right" valign="top">userInfoCache</td>
041: * <td>
042: * The {@link java.util.Map} to use for result caching. This class does no cache
043: * maintenence. It is assumed the underlying Map implementation will ensure the cache
044: * is in a good state at all times.
045: * </td>
046: * <td valign="top">No</td>
047: * <td valign="top">Value returned by 'CacheFactoryLocator.getCacheFactory().getCache(CacheFactory.USER_INFO_CACHE)'</td>
048: * </tr>
049: * <tr>
050: * <td align="right" valign="top">cacheKeyAttributes</td>
051: * <td>
052: * A Set of attribute names to use when building the cache key. The default
053: * implementation generates the key as a Map of attributeNames to values retrieved
054: * from the seed for the query. Zero length sets are treaded as null.
055: * </td>
056: * <td valign="top">No</td>
057: * <td valign="top">null</td>
058: * </tr>
059: * </table>
060: *
061: *
062: * @author dgrimwood@unicon.net
063: * @author Eric Dalquist <a href="mailto:edalquist@unicon.net">edalquist@unicon.net</a>
064: * @version $Id
065: */
066: public class CachingPersonAttributeDaoImpl extends
067: AbstractDefaultQueryPersonAttributeDao {
068: private long queries = 0;
069: private long misses = 0;
070:
071: /*
072: * The IPersonAttributeDao to delegate cache misses to.
073: */
074: private IPersonAttributeDao cachedPersonAttributesDao;
075:
076: /*
077: * The cache to store query results in.
078: */
079: private Map userInfoCache = null;
080:
081: /*
082: * The set of attributes to use to generate the cache key.
083: */
084: private Set cacheKeyAttributes = null;
085:
086: /**
087: * @return Returns the cachedPersonAttributesDao.
088: */
089: public IPersonAttributeDao getCachedPersonAttributesDao() {
090: return this .cachedPersonAttributesDao;
091: }
092:
093: /**
094: * @param cachedPersonAttributesDao The cachedPersonAttributesDao to set.
095: */
096: public void setCachedPersonAttributesDao(
097: IPersonAttributeDao cachedPersonAttributesDao) {
098: if (cachedPersonAttributesDao == null) {
099: throw new IllegalArgumentException(
100: "cachedPersonAttributesDao may not be null");
101: }
102:
103: this .cachedPersonAttributesDao = cachedPersonAttributesDao;
104: }
105:
106: /**
107: * @return Returns the cacheKeyAttributes.
108: */
109: public Set getCacheKeyAttributes() {
110: return this .cacheKeyAttributes;
111: }
112:
113: /**
114: * @param cacheKeyAttributes The cacheKeyAttributes to set.
115: */
116: public void setCacheKeyAttributes(Set cacheKeyAttributes) {
117: this .cacheKeyAttributes = cacheKeyAttributes;
118: }
119:
120: /**
121: * @return Returns the userInfoCache.
122: */
123: public Map getUserInfoCache() {
124: return this .userInfoCache;
125: }
126:
127: /**
128: * @param userInfoCache The userInfoCache to set.
129: */
130: public void setUserInfoCache(Map userInfoCache) {
131: if (userInfoCache == null) {
132: throw new IllegalArgumentException(
133: "userInfoCache may not be null");
134: }
135:
136: this .userInfoCache = userInfoCache;
137: }
138:
139: /**
140: * @return Returns the number of cache misses.
141: */
142: public long getMisses() {
143: return this .misses;
144: }
145:
146: /**
147: * @return Returns the number of queries.
148: */
149: public long getQueries() {
150: return this .queries;
151: }
152:
153: /**
154: * Wraps the call to the specified cachedPersonAttributesDao IPersonAttributeDao delegate with
155: * a caching layer. Results are cached using keys generated by {@link #getCacheKey(Map)}.
156: *
157: * @see org.jasig.portal.services.persondir.IPersonAttributeDao#getUserAttributes(java.util.Map)
158: */
159: public Map getUserAttributes(Map seed) {
160: //Ensure the arguements and state are valid
161: if (seed == null) {
162: throw new IllegalArgumentException(
163: "The query seed Map cannot be null.");
164: }
165:
166: if (this .userInfoCache == null) {
167: this .userInfoCache = CacheFactoryLocator.getCacheFactory()
168: .getCache(CacheFactory.USER_INFO_CACHE);
169: }
170:
171: if (this .cachedPersonAttributesDao == null) {
172: throw new IllegalStateException(
173: "No 'cachedPersonAttributesDao' has been specified.");
174: }
175: if (this .userInfoCache == null) {
176: throw new IllegalStateException(
177: "No 'userInfoCache' has been specified.");
178: }
179:
180: final Object cacheKey = this .getCacheKey(seed);
181:
182: if (cacheKey != null) {
183: final Map cacheResults = (Map) this .userInfoCache
184: .get(cacheKey);
185: if (cacheResults != null) {
186: if (log.isDebugEnabled()) {
187: log.debug("Retrieved query from cache. key='"
188: + cacheKey + "', results='" + cacheResults
189: + "'");
190: }
191:
192: this .queries++;
193: if (log.isDebugEnabled()) {
194: log.debug("Cache Stats: queries=" + this .queries
195: + ", hits=" + (this .queries - this .misses)
196: + ", misses=" + this .misses);
197: }
198:
199: return cacheResults;
200: }
201:
202: final Map queryResults = this .cachedPersonAttributesDao
203: .getUserAttributes(seed);
204:
205: this .userInfoCache.put(cacheKey, queryResults);
206:
207: if (log.isDebugEnabled()) {
208: log
209: .debug("Retrieved query from wrapped IPersonAttributeDao and stored in cache. key='"
210: + cacheKey
211: + "', results='"
212: + queryResults + "'");
213: }
214:
215: this .queries++;
216: this .misses++;
217: if (log.isDebugEnabled()) {
218: log.debug("Cache Stats: queries=" + this .queries
219: + ", hits=" + (this .queries - this .misses)
220: + ", misses=" + this .misses);
221: }
222:
223: return queryResults;
224: } else {
225: log
226: .warn("No cache key generated, caching disabled for this query. query='"
227: + seed
228: + "', cacheKeyAttributes="
229: + this .cacheKeyAttributes
230: + "', defaultAttributeName='"
231: + this .getDefaultAttributeName() + "'");
232:
233: this .queries++;
234: this .misses++;
235: if (log.isDebugEnabled()) {
236: log.debug("Cache Stats: queries=" + this .queries
237: + ", hits=" + (this .queries - this .misses)
238: + ", misses=" + this .misses);
239: }
240:
241: return this .cachedPersonAttributesDao
242: .getUserAttributes(seed);
243: }
244: }
245:
246: /**
247: * @see org.jasig.portal.services.persondir.IPersonAttributeDao#getPossibleUserAttributeNames()
248: */
249: public Set getPossibleUserAttributeNames() {
250: return this .cachedPersonAttributesDao
251: .getPossibleUserAttributeNames();
252: }
253:
254: /**
255: * Generates a Serializable cache key from the seed parameters according to the documentation
256: * of this class. If the return value is NULL caching will be disabled for this query.
257: *
258: * @param querySeed The query to base the key on.
259: * @return A Serializable cache key.
260: */
261: protected Serializable getCacheKey(Map querySeed) {
262: final HashMap cacheKey = new HashMap();
263:
264: if (this .cacheKeyAttributes == null
265: || this .cacheKeyAttributes.size() == 0) {
266: final String defaultAttrName = this
267: .getDefaultAttributeName();
268:
269: if (defaultAttrName == null) {
270: throw new IllegalStateException(
271: "Both 'defaultAttrName' and 'cacheKeyAttributes' properties may not be null.");
272: }
273:
274: cacheKey.put(defaultAttrName, querySeed
275: .get(defaultAttrName));
276:
277: if (log.isDebugEnabled()) {
278: log.debug("Created cacheKey='" + cacheKey
279: + "' from query='" + querySeed
280: + "' using default attribute='"
281: + defaultAttrName + "'");
282: }
283: } else {
284: for (final Iterator attrItr = this .cacheKeyAttributes
285: .iterator(); attrItr.hasNext();) {
286: final String attr = (String) attrItr.next();
287: cacheKey.put(attr, querySeed.get(attr));
288: }
289:
290: if (log.isDebugEnabled()) {
291: log.debug("Created cacheKey='" + cacheKey
292: + "' from query='" + querySeed
293: + "' using attributes='"
294: + this .cacheKeyAttributes + "'");
295: }
296: }
297:
298: if (cacheKey.size() > 0) {
299: return cacheKey;
300: } else {
301: return null;
302: }
303: }
304: }
|