001: /*
002: * <copyright>
003: *
004: * Copyright 2001-2004 Mobile Intelligence Corp
005: * under sponsorship of the Defense Advanced Research Projects
006: * Agency (DARPA).
007: *
008: * You can redistribute this software and/or modify it under the
009: * terms of the Cougaar Open Source License as published on the
010: * Cougaar Open Source Website (www.cougaar.org).
011: *
012: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
013: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
014: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
015: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
016: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
017: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
018: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
019: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
020: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
021: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
022: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
023: *
024: * </copyright>
025: */
026:
027: package org.cougaar.community;
028:
029: import java.util.Collection;
030: import java.util.Collections;
031: import java.util.Date;
032: import java.util.HashMap;
033: import java.util.HashSet;
034: import java.util.Iterator;
035: import java.util.Map;
036: import java.util.Set;
037: import java.text.DateFormat;
038: import java.text.SimpleDateFormat;
039:
040: import org.cougaar.core.service.ThreadService;
041: import org.cougaar.core.service.community.Community;
042: import org.cougaar.core.service.community.Entity;
043: import org.cougaar.core.service.community.CommunityChangeEvent;
044: import org.cougaar.core.service.community.CommunityChangeListener;
045: import org.cougaar.util.log.Logger;
046: import org.cougaar.util.log.LoggerFactory;
047:
048: import javax.naming.directory.Attributes;
049:
050: /**
051: * Maintains a local cache of Community objects.
052: */
053: public class CommunityCache implements CommunityServiceConstants {
054:
055: protected Logger logger = LoggerFactory.getInstance().createLogger(
056: CommunityCache.class);
057: protected Map communities = Collections
058: .synchronizedMap(new HashMap());
059: protected Map listenerMap = Collections
060: .synchronizedMap(new HashMap());
061: protected ThreadService threadService;
062: protected long expirationPeriod = DEFAULT_CACHE_EXPIRATION;
063:
064: private static DateFormat df = new SimpleDateFormat("HH:mm:ss,SSS");
065:
066: public CommunityCache(ThreadService ts) {
067: this .threadService = ts;
068: getSystemProperties();
069: }
070:
071: public CommunityCache(ThreadService ts, long expiration) {
072: this (ts);
073: this .expirationPeriod = expiration;
074: }
075:
076: protected void getSystemProperties() {
077: try {
078: expirationPeriod = Long.parseLong(System.getProperty(
079: CACHE_EXPIRATION_PROPERTY, Long
080: .toString(DEFAULT_CACHE_EXPIRATION)));
081: } catch (Exception ex) {
082: if (logger.isWarnEnabled()) {
083: logger
084: .warn(
085: "Exception setting parameter from system property",
086: ex);
087: }
088: }
089: }
090:
091: public synchronized Community get(String name) {
092: //TODO: Add authorization check
093: Community community = null;
094: CacheEntry ce = (CacheEntry) communities.get(name);
095: if (ce != null) {
096: if (isExpired(ce)) {
097: // Flush cache entry if expired
098: flushCacheEntry(ce);
099: } else {
100: community = (CommunityImpl) ce.community;
101: }
102: }
103: return community;
104: }
105:
106: /**
107: * Searches all communities in cache for a community matching search filter
108: * @param filter JNDI-compliant search filter
109: * @return Set of Community object matching search criteria
110: */
111: public synchronized Set search(String filter) {
112: //TODO: Add authorization check
113: if (communities.isEmpty())
114: return null;
115: Set matches = new HashSet();
116: try {
117: Filter f = new SearchStringParser().parse(filter);
118: for (Iterator it = communities.values().iterator(); it
119: .hasNext();) {
120: CacheEntry ce = (CacheEntry) it.next();
121: CommunityImpl community = ce.community;
122: if (f.match(community.getAttributes()))
123: matches.add(community);
124: }
125: } catch (Exception ex) {
126: System.out.println("Exception in search, filter=" + filter);
127: ex.printStackTrace();
128: }
129: if (logger.isDetailEnabled())
130: logger.detail("search: matches="
131: + CommunityUtils.entityNames(matches));
132: return matches;
133: }
134:
135: /**
136: * Searches community for all entities matching search filter
137: * @param communityName Name of community to search
138: * @param filter JNDI-compliant search filter
139: * @param qualifier Restict returned Entities to AGENTS_ONLY, COMMUNITIES_ONLY,
140: * or ALL_ENTITIES (refer to org.cougaar.core.service.community.Community
141: * for values)
142: * @param recursive Controls whether search includes nested communities if any
143: * @return Set of Entity objects matching search criteria
144: */
145: public Set search(String communityName, String filter,
146: int qualifier, boolean recursive) {
147: Community community = get(communityName);
148: if (logger.isDetailEnabled()) {
149: logger.detail("search:" + " community=" + communityName
150: + " filter=" + filter + " qualifier="
151: + community.qualifierToString(qualifier)
152: + " recursive=" + recursive);
153: }
154: if (community == null)
155: return Collections.EMPTY_SET;
156: if (recursive) {
157: Set matches = new HashSet();
158: recursiveSearch(community, filter, qualifier, matches,
159: new HashSet());
160: return matches;
161: } else { // Recursive search
162: return community.search(filter, qualifier);
163: }
164: }
165:
166: /*
167: * Recursive search of community map for all ancestors of a specified entity.
168: */
169: private synchronized void findAncestors(String entityName,
170: Set ancestors, boolean recursive) {
171: Collection allCommunities = communities.values();
172: if (logger.isDetailEnabled()) {
173: logger.detail("findAncestors:" + " entity=" + entityName
174: + " ancestors=" + ancestors + " communities="
175: + allCommunities.size());
176: }
177: for (Iterator it = allCommunities.iterator(); it.hasNext();) {
178: CacheEntry ce = (CacheEntry) it.next();
179: Community community = ce.community;
180: if (community.hasEntity(entityName)) {
181: String parent = community.getName();
182: ancestors.add(parent);
183: if (recursive)
184: findAncestors(parent, ancestors, recursive);
185: }
186: }
187: }
188:
189: public synchronized void update(Community community) {
190: //TODO: Add authorization check
191: CommunityImpl ci = (CommunityImpl) community;
192: CacheEntry ce = (CacheEntry) communities.get(community
193: .getName());
194: if (ce != null) {
195: if (ci.getLastUpdate() >= ce.community.getLastUpdate()) {
196: ce.timeStamp = now();
197: //CommunityImpl prior = ce.community;
198: //ce.community = (CommunityImpl)community;
199: if (logger.isDebugEnabled()) {
200: logger.debug("update:"
201: + " community="
202: + community.getName()
203: + " prior="
204: + (ce.community == null ? -1 : ce.community
205: .getEntities().size())
206: + " updated="
207: + ce.community.getEntities().size()
208: + " expires="
209: + (expirationPeriod == NEVER ? "NEVER" : df
210: .format(new Date(ce.timeStamp
211: + expirationPeriod))));
212: }
213: if (logger.isDetailEnabled()) {
214: logger.detail(this .toString());
215: }
216: fireChangeNotifications(ce.community, community);
217: }
218: } else {
219: ce = new CacheEntry(now(), (CommunityImpl) ci.clone());
220: communities.put(community.getName(), ce);
221: if (logger.isDebugEnabled()) {
222: logger.debug("add:"
223: + " community="
224: + community.getName()
225: + " prior=null"
226: + " updated="
227: + ce.community.getEntities().size()
228: + " expires="
229: + (expirationPeriod == NEVER ? "NEVER" : df
230: .format(new Date(ce.timeStamp
231: + expirationPeriod))));
232: }
233: if (logger.isDetailEnabled()) {
234: logger.detail(this .toString());
235: }
236: fireChangeNotifications(ce.community, null);
237: }
238: }
239:
240: public synchronized String toString() {
241: return "CommunityCache: contents="
242: + communities.keySet().toString();
243: }
244:
245: public synchronized String toXML() {
246: //TODO: Add authorization check
247: StringBuffer sb = new StringBuffer();
248: for (Iterator it = communities.values().iterator(); it
249: .hasNext();) {
250: CacheEntry ce = (CacheEntry) it.next();
251: sb.append(ce.community.toXml());
252: }
253: return sb.toString();
254: }
255:
256: public synchronized Set listAll() {
257: //TODO: Add authorization check
258: return new HashSet(communities.keySet());
259: }
260:
261: /**
262: * Searches cache for all ancestors of specified entity.
263: * @param entityName Entity name
264: * @param recursive If true all ancestors are retrieved, if false only immediate
265: * parents
266: * @return List of communities having specified community as a descendent
267: */
268: public Set getAncestorNames(String entityName, boolean recursive) {
269: //TODO: Add authorization check
270: Set ancestors = new HashSet();
271: if (logger.isDetailEnabled()) {
272: logger.detail("getAncestorNames:" + " entity=" + entityName
273: + " recursive=" + recursive + " ancestors="
274: + ancestors);
275: }
276: findAncestors(entityName, ancestors, recursive);
277: return ancestors;
278: }
279:
280: /**
281: * Add listener to be notified when a change occurs to community.
282: * @param l Listener to be notified
283: */
284: public void addListener(CommunityChangeListener l) {
285: if (l != null)
286: addListener(l.getCommunityName(), l);
287: }
288:
289: /**
290: * Removes listener from change notification list.
291: * @param l Listener to be removed
292: */
293: public boolean removeListener(CommunityChangeListener l) {
294: if (l != null) {
295: String communityName = l.getCommunityName();
296: if (l == null) {
297: communityName = "ALL_COMMUNITIES";
298: }
299: if (logger.isDetailEnabled()) {
300: logger.detail("removeListener: community="
301: + communityName);
302: }
303: synchronized (listenerMap) {
304: Set listeners = (Set) listenerMap.get(communityName);
305: if (listeners != null && listeners.contains(l)) {
306: listeners.remove(l);
307: return true;
308: }
309: }
310: }
311: return false;
312: }
313:
314: public synchronized boolean contains(String name) {
315: boolean containsCurrentEntry = false;
316: CacheEntry ce = (CacheEntry) communities.get(name);
317: if (ce != null) {
318: if (isExpired(ce)) {
319: // Flush cache entry if expired
320: flushCacheEntry(ce);
321: } else {
322: containsCurrentEntry = true;
323: }
324: }
325: return containsCurrentEntry;
326: }
327:
328: public synchronized Community remove(String communityName) {
329: if (logger.isDebugEnabled()) {
330: logger.debug("remove:" + " community=" + communityName);
331: }
332: CacheEntry ce = (CacheEntry) communities.remove(communityName);
333: return (ce == null ? null : ce.community);
334: }
335:
336: private void fireChangeNotifications(Community current,
337: Community updated) {
338: if (logger.isDetailEnabled()) {
339: logger.detail("fireChangeNotifications: community="
340: + (updated == null ? "null" : updated.getName()));
341: }
342: //Community community = get(updated.getName()); // a copy for Change Event
343: if (updated == null) { // new community
344: notifyListeners(new CommunityChangeEvent(current,
345: CommunityChangeEvent.ADD_COMMUNITY, current
346: .getName()));
347: for (Iterator it = current.getEntities().iterator(); it
348: .hasNext();) {
349: Entity entity = (Entity) it.next();
350: notifyListeners(new CommunityChangeEvent(current,
351: CommunityChangeEvent.ADD_ENTITY, entity
352: .getName()));
353: }
354: } else {
355:
356: // Updated community attributes
357: if (!attributesEqual(current.getAttributes(), updated
358: .getAttributes())) {
359: current.setAttributes((Attributes) updated
360: .getAttributes().clone());
361: notifyListeners(new CommunityChangeEvent(
362: current,
363: CommunityChangeEvent.COMMUNITY_ATTRIBUTES_CHANGED,
364: current.getName()));
365: }
366:
367: // Added Entities
368: Collection addedEntities = listAddedEntities(current
369: .getEntities(), updated.getEntities());
370: for (Iterator it = addedEntities.iterator(); it.hasNext();) {
371: String entityName = (String) it.next();
372: current.addEntity(updated.getEntity(entityName));
373: notifyListeners(new CommunityChangeEvent(current,
374: CommunityChangeEvent.ADD_ENTITY, entityName));
375: }
376:
377: // Removed Entities
378: Collection removedEntities = listRemovedEntities(current
379: .getEntities(), updated.getEntities());
380: for (Iterator it = removedEntities.iterator(); it.hasNext();) {
381: String entityName = (String) it.next();
382: current.removeEntity(entityName);
383: notifyListeners(new CommunityChangeEvent(current,
384: CommunityChangeEvent.REMOVE_ENTITY, entityName));
385: }
386:
387: // Entities with changed attributes
388: for (Iterator it = current.getEntities().iterator(); it
389: .hasNext();) {
390: Entity curEntity = (Entity) it.next();
391: Entity updatedEntity = updated.getEntity(curEntity
392: .getName());
393: if (updatedEntity != null
394: && !attributesEqual(curEntity.getAttributes(),
395: updatedEntity.getAttributes())) {
396: curEntity.setAttributes((Attributes) updatedEntity
397: .getAttributes().clone());
398: notifyListeners(new CommunityChangeEvent(
399: current,
400: CommunityChangeEvent.ENTITY_ATTRIBUTES_CHANGED,
401: curEntity.getName()));
402: }
403: }
404: }
405: }
406:
407: private boolean attributesEqual(Attributes attrs1, Attributes attrs2) {
408: return (attrs1 == null && attrs2 == null) || attrs1 != null
409: && attrs1.equals(attrs2);
410: }
411:
412: private Collection listAddedEntities(Collection prior,
413: Collection current) {
414: Collection added = CommunityUtils.getEntityNames(current);
415: added.removeAll(CommunityUtils.getEntityNames(prior));
416: return added;
417: }
418:
419: private Collection listRemovedEntities(Collection prior,
420: Collection current) {
421: Collection removed = CommunityUtils.getEntityNames(prior);
422: removed.removeAll(CommunityUtils.getEntityNames(current));
423: return removed;
424: }
425:
426: private long now() {
427: return System.currentTimeMillis();
428: }
429:
430: private boolean isExpired(CacheEntry ce) {
431: return (expirationPeriod != NEVER && (ce.timeStamp + expirationPeriod) < now());
432: }
433:
434: private void flushCacheEntry(CacheEntry ce) {
435: if (logger.isInfoEnabled()) {
436: logger.info("flushEntry: community="
437: + ce.community.getName());
438: }
439: remove(ce.community.getName());
440: }
441:
442: /*
443: * Determines if a local copy exists for all nested communities from a
444: * specified root community.
445: */
446: private synchronized boolean allDescendentsFound(Community community) {
447: Collection nestedCommunities = community.search(
448: "(Role=Member)", Community.COMMUNITIES_ONLY);
449: for (Iterator it = nestedCommunities.iterator(); it.hasNext();) {
450: Community nestedCommunity = (Community) it.next();
451: if (!communities.containsKey(nestedCommunity.getName())
452: || !allDescendentsFound(nestedCommunity))
453: return false;
454: }
455: return true;
456: }
457:
458: private void recursiveSearch(Community community, String filter,
459: int qualifier, Set matches, Set visited) {
460: if (community != null) {
461: visited.add(community.getName()); // avoid endless loop caused by circular references
462: Collection entities = community.search(filter, qualifier);
463: if (logger.isDetailEnabled()) {
464: logger.detail("recursiveSearch:" + " community="
465: + community.getName() + " filter=" + filter
466: + " qualifier=" + qualifier + " matches="
467: + CommunityUtils.entityNames(entities));
468: }
469: matches.addAll(entities);
470: for (Iterator it = community.getEntities().iterator(); it
471: .hasNext();) {
472: Entity entity = (Entity) it.next();
473: if (entity instanceof Community) {
474: String nestedCommunityName = entity.getName();
475: Community nestedCommunity = get(nestedCommunityName);
476: if (nestedCommunity != null
477: && !visited.contains(nestedCommunity
478: .getName())) {
479: recursiveSearch(nestedCommunity, filter,
480: qualifier, matches, visited);
481: }
482: }
483: }
484: }
485: }
486:
487: /**
488: * Invoke callback on each CommunityListener associated with named
489: * community and its ancestors. Provide community reference in callback
490: * argument.
491: * @param cce CommunityChangeEvent to fire
492: */
493: private void notifyListeners(CommunityChangeEvent cce) {
494: Set listenerSet = new HashSet();
495: Set affectedCommunities = new HashSet();
496: affectedCommunities.add(cce.getCommunityName());
497: affectedCommunities.addAll(getAncestorNames(cce
498: .getCommunityName(), true));
499: for (Iterator it = affectedCommunities.iterator(); it.hasNext();) {
500: Set listeners = getListeners((String) it.next());
501: for (Iterator it1 = listeners.iterator(); it1.hasNext();) {
502: listenerSet.add((CommunityChangeListener) it1.next());
503: }
504: }
505: Set listeners = getListeners("ALL_COMMUNITIES");
506: for (Iterator it = listeners.iterator(); it.hasNext();) {
507: listenerSet.add((CommunityChangeListener) it.next());
508: }
509: if (logger.isDetailEnabled()) {
510: logger.detail("notifyListeners:"
511: + " community="
512: + cce.getCommunityName()
513: + " changeType="
514: + CommunityChangeEvent.getChangeTypeAsString(cce
515: .getType()) + " whatChanged="
516: + cce.getWhatChanged() + " numListeners="
517: + listenerSet.size());
518: }
519: fireCommunityChangeEvent(listenerSet, cce);
520: }
521:
522: private void fireCommunityChangeEvent(CommunityChangeListener l,
523: CommunityChangeEvent cce) {
524: Set listeners = new HashSet();
525: listeners.add(l);
526: fireCommunityChangeEvent(listeners, cce);
527: }
528:
529: private void fireCommunityChangeEvent(final Set listeners,
530: final CommunityChangeEvent cce) {
531: if (threadService != null) { // use Cougaar threads
532: threadService.getThread(this , new Runnable() {
533: public void run() {
534: for (Iterator it = listeners.iterator(); it
535: .hasNext();) {
536: ((CommunityChangeListener) it.next())
537: .communityChanged(cce);
538: }
539: }
540: }, "CommunityNotificationThread").start();
541: } else { // Use regular Java threads
542: new Thread("CommunityNotificationThread") {
543: public void run() {
544: for (Iterator it = listeners.iterator(); it
545: .hasNext();) {
546: ((CommunityChangeListener) it.next())
547: .communityChanged(cce);
548: }
549: }
550: }.start();
551: }
552: }
553:
554: private synchronized void addListener(String communityName,
555: CommunityChangeListener l) {
556: if (l != null) {
557: String cname = (communityName != null ? communityName
558: : "ALL_COMMUNITIES");
559: if (logger.isDetailEnabled()) {
560: logger.detail("addListeners:" + " community=" + cname);
561: }
562: synchronized (listenerMap) {
563: Set listeners = (Set) listenerMap.get(cname);
564: if (listeners == null) {
565: listeners = new HashSet();
566: listenerMap.put(cname, listeners);
567: }
568: listeners.add(l);
569: // If listener is interested in communities which are already in cache
570: // send an initial event
571: if (cname.equals("ALL_COMMUNITIES")) {
572: for (Iterator it = communities.values().iterator(); it
573: .hasNext();) {
574: CacheEntry ce = (CacheEntry) it.next();
575: Community community = ce.community;
576: fireCommunityChangeEvent(
577: l,
578: new CommunityChangeEvent(
579: community,
580: CommunityChangeEvent.ADD_COMMUNITY,
581: community.getName()));
582: for (Iterator it1 = community.getEntities()
583: .iterator(); it1.hasNext();) {
584: Entity entity = (Entity) it1.next();
585: fireCommunityChangeEvent(
586: l,
587: new CommunityChangeEvent(
588: community,
589: CommunityChangeEvent.ADD_ENTITY,
590: entity.getName()));
591: }
592: }
593: } else {
594: Community community = get(cname);
595: if (community != null) {
596: fireCommunityChangeEvent(
597: l,
598: new CommunityChangeEvent(
599: community,
600: CommunityChangeEvent.ADD_COMMUNITY,
601: community.getName()));
602: for (Iterator it = community.getEntities()
603: .iterator(); it.hasNext();) {
604: Entity entity = (Entity) it.next();
605: fireCommunityChangeEvent(
606: l,
607: new CommunityChangeEvent(
608: community,
609: CommunityChangeEvent.ADD_ENTITY,
610: entity.getName()));
611: }
612: }
613: }
614: }
615: }
616: }
617:
618: /**
619: * Gets listeners.
620: * @param communityName Name of communtiy
621: * @return Set of CommunityListeners.
622: */
623: protected Set getListeners(String communityName) {
624: synchronized (listenerMap) {
625: if (!listenerMap.containsKey(communityName)) {
626: listenerMap.put(communityName, new HashSet());
627: }
628: return new HashSet((Set) listenerMap.get(communityName));
629: }
630: }
631:
632: class CacheEntry {
633: private long timeStamp;
634: private CommunityImpl community;
635:
636: CacheEntry(long timeStamp, CommunityImpl community) {
637: this.timeStamp = timeStamp;
638: this.community = community;
639: }
640: }
641:
642: }
|