001: /*
002: * <copyright>
003: *
004: * Copyright 1997-2007 BBNT Solutions, LLC
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.core.agent.service.community;
028:
029: import java.net.URI;
030: import java.util.ArrayList;
031: import java.util.Collection;
032: import java.util.Collections;
033: import java.util.Iterator;
034: import java.util.LinkedHashMap;
035: import java.util.LinkedHashSet;
036: import java.util.List;
037: import java.util.Map;
038: import java.util.Set;
039: import javax.naming.directory.Attributes;
040: import javax.naming.directory.ModificationItem;
041: import org.cougaar.core.component.ComponentSupport;
042: import org.cougaar.core.component.ServiceBroker;
043: import org.cougaar.core.component.ServiceProvider;
044: import org.cougaar.core.mts.MessageAddress;
045: import org.cougaar.core.node.NodeControlService;
046: import org.cougaar.core.service.LoggingService;
047: import org.cougaar.core.service.ThreadService;
048: import org.cougaar.core.service.community.Community;
049: import org.cougaar.core.service.community.CommunityChangeEvent;
050: import org.cougaar.core.service.community.CommunityChangeListener;
051: import org.cougaar.core.service.community.CommunityResponse;
052: import org.cougaar.core.service.community.CommunityResponseListener;
053: import org.cougaar.core.service.community.CommunityService;
054: import org.cougaar.core.service.community.Entity;
055: import org.cougaar.core.service.community.FindCommunityCallback;
056: import org.cougaar.core.service.wp.AddressEntry;
057: import org.cougaar.core.service.wp.Callback;
058: import org.cougaar.core.service.wp.Request;
059: import org.cougaar.core.service.wp.Response;
060: import org.cougaar.core.service.wp.WhitePagesService;
061: import org.cougaar.core.thread.Schedulable;
062: import org.cougaar.util.Arguments;
063:
064: /**
065: * This component advertises a {@link CommunityService} implementation that
066: * uses the {@link WhitePagesService} to supports basic join, leave, and
067: * search operations.
068: * <p>
069: * This implementation is sufficient to support {@link AttributeBasedAddress}
070: * (ABA) relay targets. Hierarchical communities are not supported.
071: * Community members must explicitly join their community at startup, as
072: * illustrated in the {@link JoinCommunity} helper plugin.
073: */
074: public class CommunityServiceProvider extends ComponentSupport {
075:
076: // the wp is cached, so we poll at a relatively fast rate.
077: private static final long DEFAULT_REFRESH_PERIOD = 11000;
078:
079: // FIXME "search" can't return null on a cache miss, so we wait
080: // a short while to help avoid false "empty" results
081: private static final long DEFAULT_WP_TIMEOUT = 2000;
082:
083: // our marker key for listing community names, as opposed to members
084: private static final String ALL = "*";
085:
086: private static final String WP_SUFFIX = ".communities";
087: private static final String WP_TYPE = "community";
088:
089: // our marker entry for in-progress lookups
090: private static final Set PENDING = Collections.singleton("PENDING");
091:
092: private Arguments args = Arguments.EMPTY_INSTANCE;
093: private long refreshPeriod;
094: private long wpTimeout;
095:
096: private LoggingService log;
097: private ThreadService threadService;
098: private WhitePagesService wp;
099:
100: private ServiceBroker rootsb;
101: private ServiceProvider sp;
102: private Schedulable thread;
103:
104: private final Object lock = new Object();
105: private final Map cache = new LinkedHashMap();
106: private final List listeners = new ArrayList();
107:
108: public void setParameter(Object o) {
109: args = new Arguments(o);
110: }
111:
112: public void setLoggingService(LoggingService log) {
113: this .log = log;
114: }
115:
116: public void setThreadService(ThreadService threadService) {
117: this .threadService = threadService;
118: }
119:
120: public void setWhitePagesService(WhitePagesService wp) {
121: this .wp = wp;
122: }
123:
124: public void load() {
125: super .load();
126:
127: refreshPeriod = args.getLong("refreshPeriod",
128: DEFAULT_REFRESH_PERIOD);
129: wpTimeout = args.getLong("wpTimeout", DEFAULT_WP_TIMEOUT);
130:
131: ServiceBroker sb = getServiceBroker();
132:
133: // optional root-level sb
134: NodeControlService ncs = (NodeControlService) sb.getService(
135: this , NodeControlService.class, null);
136: if (ncs != null) {
137: rootsb = ncs.getRootServiceBroker();
138: sb.releaseService(this , NodeControlService.class, ncs);
139: }
140:
141: // advertise our service
142: sp = new TrivialServiceProvider(new CommunityServiceImpl());
143: ServiceBroker the_sb = (rootsb == null ? sb : rootsb);
144: the_sb.addService(CommunityService.class, sp);
145: }
146:
147: public void start() {
148: super .start();
149:
150: // start our refresh thread
151: Runnable runner = new Runnable() {
152: public void run() {
153: refresh();
154: }
155: };
156: thread = threadService.getThread(this , runner,
157: "CommunityService refresh poller");
158: thread.schedule(refreshPeriod);
159: }
160:
161: public void stop() {
162: super .stop();
163:
164: // stop our refresh thread
165: if (thread != null) {
166: thread.cancel();
167: thread = null;
168: }
169: }
170:
171: public void unload() {
172: super .unload();
173:
174: // revoke our service
175: if (sp != null) {
176: ServiceBroker the_sb = (rootsb == null ? getServiceBroker()
177: : rootsb);
178: the_sb.revokeService(CommunityService.class, sp);
179: sp = null;
180: }
181: }
182:
183: //
184: // the following methods are called by our CommunityServiceImpl
185: //
186:
187: private void add(CommunityChangeListener ccl) {
188: synchronized (lock) {
189: // wrap to ensure that it's well behaved
190: CommunityChangeListener l = new ListenerImpl(ccl);
191: if (listeners.contains(l))
192: return;
193: if (log.isDebugEnabled()) {
194: log.debug("Add listener " + l);
195: }
196: listeners.add(l);
197: }
198: }
199:
200: private void remove(CommunityChangeListener ccl) {
201: synchronized (lock) {
202: CommunityChangeListener l = new ListenerImpl(ccl);
203: if (!listeners.contains(l))
204: return;
205: if (log.isDebugEnabled()) {
206: log.debug("Remove listener " + l);
207: }
208: listeners.remove(l);
209: }
210: }
211:
212: // called by periodic thread
213: private void refresh() {
214: // get list of community names that we should refresh (null means all)
215: Set names;
216: synchronized (lock) {
217: if (listeners.isEmpty()) {
218: names = Collections.EMPTY_SET;
219: } else {
220: names = null;
221: for (int i = 0; i < listeners.size(); i++) {
222: CommunityChangeListener li = (CommunityChangeListener) listeners
223: .get(i);
224: String ci = li.getCommunityName();
225: ci = fix(ci);
226: if (ci.length() == 0) {
227: // all
228: names = null;
229: break;
230: }
231: if (names == null) {
232: names = new LinkedHashSet();
233: }
234: names.add(ci);
235: }
236: }
237:
238: if (names != null) {
239: // not all
240: for (Iterator iter = cache.entrySet().iterator(); iter
241: .hasNext();) {
242: Map.Entry me = (Map.Entry) iter.next();
243: String ci = (String) me.getKey();
244: // remove entries we're not listening to (and are not pending)
245: if (!names.contains(ci)
246: && (me.getValue() != PENDING)) {
247: iter.remove();
248: }
249: }
250: }
251: }
252:
253: if (names == null) {
254: // get all, force lookup
255: names = list(ALL, false, null);
256: if (names == null) {
257: // cache miss, we'll get it next time
258: names = Collections.EMPTY_SET;
259: }
260: }
261:
262: // force lookup
263: for (Iterator iter = names.iterator(); iter.hasNext();) {
264: String ci = (String) iter.next();
265: list(ci, false, null);
266: }
267:
268: // wake again later
269: if (thread != null) {
270: thread.schedule(refreshPeriod);
271: }
272: }
273:
274: /**
275: * @param community either ALL or a non-null, non-empty String
276: * @return a Set of Strings if ALL, otherwise a Set of MessageAddresses
277: * if not ALL. This set can be null if !useCache.
278: */
279: private Set list(final String community, boolean useCache,
280: final CommunityResponseListener crl) {
281: // check cache
282: Set oldC;
283: synchronized (lock) {
284: oldC = (Set) cache.get(community);
285: if (oldC == null) {
286: // new lookup
287: cache.put(community, PENDING);
288: }
289: }
290: if (oldC != null) {
291: if (oldC == PENDING) {
292: // already in progress
293: return (useCache ? Collections.EMPTY_SET : null); // FIXME see below.
294: } else if (useCache) {
295: // in cache
296: return oldC;
297: } else {
298: // do refresh, but don't replace our cached value with PENDING
299: }
300: }
301:
302: if (log.isDebugEnabled()) {
303: log.debug((useCache ? "Looking up" : "Refreshing") + " "
304: + community);
305: }
306:
307: // initiate lookup
308: String ext = WP_SUFFIX;
309: if (community != ALL) {
310: ext = "." + community + WP_SUFFIX;
311: }
312: Callback cb = new Callback() {
313: public void execute(Response res) {
314: Set members = extractNames(res, community);
315: handleSearchResult(community, members, crl);
316: }
317: };
318: Response res = wp.submit(new Request.List(Request.NONE, ext),
319: cb);
320:
321: // check for immediate response (i.e. in local wp cache)
322: Set ret = extractNames(res, community);
323: if (ret != null)
324: return ret;
325:
326: if (!useCache) {
327: // return the stale value (null or non-PENDING)
328: return oldC;
329: }
330:
331: // FIXME the CommunityService API should return null if there's a cache
332: // miss, otherwise the caller can't distinguish between a cache miss and
333: // an empty/non-existent community!
334: //
335: // However, the CommunityService is currently defined to return an empty
336: // set on a cache miss, and the Blackboard "lookupABA(...)" method expects
337: // a non-null return value.
338: //
339: // So, for now, we must return a non-null value.
340: if (wpTimeout > 0) {
341: try {
342: res.waitForIsAvailable(wpTimeout);
343: } catch (InterruptedException ie) {
344: }
345: ret = extractNames(res, community);
346: if (ret != null)
347: return ret;
348: }
349: return Collections.EMPTY_SET; // should be null!
350: }
351:
352: // convert "[A.MyComm.communities, B.MyComm.communities]" into "[A, B]"
353: //
354: // also works for "[.MyComm.communities]" --> "[MyComm]"
355: private static final Set extractNames(Response res, String community) {
356: if (!res.isSuccess())
357: return null;
358: Set set = ((Response.List) res).getNames();
359: if (set == null)
360: return null;
361: if (set.isEmpty())
362: return Collections.EMPTY_SET;
363: String ext = WP_SUFFIX;
364: if (community != ALL) {
365: ext = "." + community + WP_SUFFIX;
366: }
367: Set ret = new LinkedHashSet();
368: for (Iterator iter = set.iterator(); iter.hasNext();) {
369: String s = (String) iter.next();
370: if (!s.endsWith(ext))
371: continue;
372: s = s.substring(0, s.length() - ext.length());
373: Object o;
374: if (community == ALL) {
375: if (s.length() <= 1 || s.charAt(0) != '.')
376: continue;
377: s = s.substring(1);
378: o = s;
379: } else {
380: o = MessageAddress.getMessageAddress(s);
381: }
382: ret.add(o);
383: }
384: return Collections.unmodifiableSet(ret);
385: }
386:
387: private void handleSearchResult(String community, Set members,
388: CommunityResponseListener crl) {
389:
390: // gather change listeners
391: List l = Collections.EMPTY_LIST;
392:
393: if (members == null) {
394: // lookup failed
395: if (log.isDebugEnabled()) {
396: log.debug("Lookup for " + community + " failed");
397: }
398: } else {
399: synchronized (lock) {
400: Set oldC = (Set) cache.get(community);
401:
402: boolean changed = (oldC == PENDING || !members
403: .equals(oldC));
404: if (changed) {
405: // modify cache
406: cache.put(community, members);
407: if (community != ALL) {
408: // find interested change listeners
409: for (int i = 0; i < listeners.size(); i++) {
410: CommunityChangeListener li = (CommunityChangeListener) listeners
411: .get(i);
412: String ci = li.getCommunityName();
413: ci = fix(ci);
414: if (ci.length() == 0
415: || ci.equals(community)) {
416: if (l.isEmpty()) {
417: l = new ArrayList();
418: }
419: l.add(li);
420: }
421: }
422: }
423: }
424:
425: if (log.isDebugEnabled()) {
426: log
427: .debug("Lookup for "
428: + community
429: + " found "
430: + (!changed ? "no change"
431: : (((oldC == null || oldC == PENDING) ? "initial"
432: : "modified")
433: + " membership list["
434: + members.size()
435: + "]:\n "
436: + members + (community == ALL ? ""
437: : ("\nTelling "
438: + l.size()
439: + " listener" + (l
440: .size() == 1 ? ""
441: : "s"))))));
442: }
443: }
444: }
445:
446: // tell search listener
447: if (crl != null) {
448: crl.getResponse(new CommunityResponseImpl(
449: (members != null), community));
450: }
451:
452: if (l.isEmpty()) {
453: // no change listeners
454: return;
455: }
456:
457: // create event
458: CommunityChangeEvent cce = new CommunityChangeEvent(
459: new CommunityImpl(community), -1, null);
460:
461: // invoke listeners
462: //
463: // our listeners should be well behaved. Minimally the Blackboard
464: // does the right thing: it kicks off a pooled thread.
465: for (int i = 0; i < l.size(); i++) {
466: CommunityChangeListener li = (CommunityChangeListener) l
467: .get(i);
468: try {
469: li.communityChanged(cce);
470: } catch (Exception e) {
471: log.error("Removing listener " + li
472: + " that failed on callback for " + cce, e);
473: synchronized (lock) {
474: listeners.remove(li);
475: }
476: }
477: }
478: }
479:
480: private void modify(final boolean isJoin, final String community,
481: final String agent, final CommunityResponseListener crl) {
482: // define wp entry
483: //
484: // RFE use the uri to specify meta-data about this agent, e.g.:
485: // "?type=blah"
486: // and/or, since we don't have a manager, about its community, e.g.:
487: // "?parent=Foo"
488: String name = agent + "." + community + WP_SUFFIX;
489: URI uri = URI.create("role://member");
490: AddressEntry ae = AddressEntry.getAddressEntry(name, WP_TYPE,
491: uri);
492:
493: if (log.isDebugEnabled()) {
494: log.debug((isJoin ? "Adding" : "Removing") + " agent "
495: + agent + " " + (isJoin ? "to" : "from")
496: + " community " + community);
497: }
498:
499: // make wp callback wrapper
500: Callback cb = new Callback() {
501: public void execute(Response res) {
502: boolean isSuccess = (isJoin ? ((Response.Bind) res)
503: .didBind() : ((Response.Unbind) res)
504: .didUnbind());
505: handleModifyResult(isJoin, community, agent, crl,
506: isSuccess);
507: }
508: };
509:
510: if (isJoin) {
511: wp.rebind(ae, cb);
512: } else {
513: wp.unbind(ae, cb);
514: }
515: }
516:
517: private void handleModifyResult(boolean isJoin,
518: final String community, String agent,
519: CommunityResponseListener crl, final boolean isSuccess) {
520:
521: if (log.isDebugEnabled()) {
522: log.debug((isSuccess ? (isJoin ? "Added" : "Removed")
523: : ("Unable to " + (isJoin ? "add" : "remove")))
524: + " agent "
525: + agent
526: + " "
527: + (isJoin ? "to" : "from")
528: + " community "
529: + community);
530: }
531:
532: // update the cache
533: if (isSuccess) {
534: synchronized (lock) {
535: Set oldC = (Set) cache.get(community);
536: if (oldC != null && oldC != PENDING
537: && (isJoin != oldC.contains(agent))) {
538: // it's safer to decache then to alter the cache entry. We want our
539: // cache to match whatevers in the wp, not our half-baked view of the
540: // world.
541: if (log.isDebugEnabled()) {
542: log.debug("Decaching " + community);
543: }
544: cache.remove(community);
545: }
546: }
547: }
548:
549: // tell optional listener
550: if (crl != null) {
551: crl.getResponse(new CommunityResponseImpl(isSuccess,
552: community));
553: }
554: }
555:
556: private static final String fix(String s) {
557: String ret = s;
558: if (ret == null || ret.equals("*")) {
559: ret = "";
560: } else {
561: ret = ret.trim();
562: }
563: return ret;
564: }
565:
566: /**
567: * Our service implementation.
568: */
569: private class CommunityServiceImpl implements CommunityService {
570:
571: public void addListener(CommunityChangeListener l) {
572: if (l == null) {
573: throw new IllegalArgumentException("Null listener");
574: }
575: add(l);
576: }
577:
578: public void removeListener(CommunityChangeListener l) {
579: if (l == null) {
580: throw new IllegalArgumentException("Null listener");
581: }
582: remove(l);
583: }
584:
585: public Collection search(String communityName, String filter) {
586: return searchCommunity(communityName, filter, false,
587: Community.AGENTS_ONLY, null);
588: }
589:
590: public Collection searchCommunity(String communityName,
591: String searchFilter, boolean recursiveSearch,
592: int resultQualifier, CommunityResponseListener crl) {
593: // filter must be "(Role=Member)"
594: if (searchFilter != null
595: && !searchFilter.equals("(Role=Member)")) {
596: throw new UnsupportedOperationException(
597: "Only supports null or \"(Role=Member)\" search filter, not "
598: + searchFilter);
599: }
600: // trim community name
601: String c = fix(communityName);
602: if (c.length() == 0) {
603: throw new IllegalArgumentException(
604: "Invalid community name: " + communityName);
605: }
606: // ignore "recursiveSearch", since our "join" forces all communities to
607: // be non-hierarchical anyways i.e. no recursion necessary)
608: //
609: // our namespace is flat, so there are no children communities
610: if (resultQualifier == Community.COMMUNITIES_ONLY) {
611: return Collections.EMPTY_SET;
612: }
613: if (resultQualifier != Community.AGENTS_ONLY
614: && resultQualifier != Community.ALL_ENTITIES) {
615: throw new IllegalArgumentException(
616: "Invalid result qualifier: " + resultQualifier);
617: }
618: return list(c, true, crl);
619: }
620:
621: /** @deprecated */
622: public Collection listAllCommunities() {
623: return list(ALL, true, null);
624: }
625:
626: public void listAllCommunities(CommunityResponseListener crl) {
627: list(ALL, true, crl);
628: }
629:
630: public void joinCommunity(String communityName,
631: String entityName, int entityType,
632: Attributes entityAttrs, boolean createIfNotFound,
633: Attributes newCommunityAttrs,
634: CommunityResponseListener crl) {
635: // trim community name
636: String c = fix(communityName);
637: if (c.length() == 0) {
638: throw new IllegalArgumentException(
639: "Invalid community name: " + communityName);
640: }
641: // trim agent name
642: String a = fix(entityName);
643: if (a.length() == 0) {
644: throw new IllegalArgumentException(
645: "Invalid agent name: " + entityName);
646: }
647: // must be of type AGENT
648: if (entityType != AGENT) {
649: throw new UnsupportedOperationException(
650: "Only supports AGENT (" + AGENT
651: + ") entities, not " + entityType);
652: }
653: // must be createIfNotFound
654: if (!createIfNotFound) {
655: throw new UnsupportedOperationException(
656: "Only supports createIfNotFound=true");
657: }
658: // no attrs
659: if (entityAttrs != null && entityAttrs.size() > 0) {
660: throw new UnsupportedOperationException(
661: "Only supports null or empty agent attributes, not "
662: + entityAttrs);
663: }
664: if (newCommunityAttrs != null
665: && newCommunityAttrs.size() > 0) {
666: throw new UnsupportedOperationException(
667: "Only supports null or empty agent attributes, not "
668: + newCommunityAttrs);
669: }
670:
671: modify(true, c, a, crl);
672: }
673:
674: public void leaveCommunity(String communityName,
675: String entityName, CommunityResponseListener crl) {
676: // trim community name
677: String c = fix(communityName);
678: if (c.length() == 0) {
679: throw new IllegalArgumentException(
680: "Invalid community name: " + communityName);
681: }
682: // trim agent name
683: String a = fix(entityName);
684: if (a.length() == 0) {
685: throw new IllegalArgumentException(
686: "Invalid agent name: " + entityName);
687: }
688:
689: modify(false, c, a, crl);
690: }
691:
692: //
693: // The rest are not supported!
694: //
695: // We could support some of the "search" methods below, e.g.:
696: // getCommunity
697: // findCommunity
698: //
699: // Create/modify don't quite make sense, since we don't have a community
700: // manager or a wp "metadata" entry that describes the overall community.
701: //
702: // We currently don't support hierarchical communities, so no "parents"
703: // for now. This could be added by having every child community entry
704: // specify its parent, e.g.
705: // AgentX.MyChildComm.communities is role://member?parent=MyParentComm
706: // However, this makes it expensive to find children.
707: //
708:
709: /** @deprecated */
710: public void createCommunity(String communityName,
711: Attributes attrs, CommunityResponseListener crl) {
712: die();
713: }
714:
715: public Community getCommunity(String communityName,
716: CommunityResponseListener crl) {
717: die();
718: return null;
719: }
720:
721: public void modifyAttributes(String communityName,
722: String entityName, ModificationItem[] mods,
723: CommunityResponseListener crl) {
724: die();
725: }
726:
727: public Collection listParentCommunities(String member,
728: CommunityResponseListener crl) {
729: die();
730: return null;
731: }
732:
733: public Collection listParentCommunities(String member,
734: String filter, CommunityResponseListener crl) {
735: die();
736: return null;
737: }
738:
739: public void findCommunity(String communityName,
740: FindCommunityCallback fccb, long timeout) {
741: die();
742: }
743:
744: /** @deprecated */
745: public String[] getParentCommunities(boolean allLevels) {
746: die();
747: return null;
748: }
749:
750: /** @deprecated */
751: public Collection listParentCommunities(String member) {
752: die();
753: return null;
754: }
755:
756: /** @deprecated */
757: public Collection listParentCommunities(String member,
758: String filter) {
759: die();
760: return null;
761: }
762:
763: private void die() {
764: throw new UnsupportedOperationException();
765: }
766: }
767:
768: /**
769: * A trivial service provider implementation, for where we want to return the
770: * same service instance to all clients.
771: */
772: private static final class TrivialServiceProvider implements
773: ServiceProvider {
774: private final Object svc;
775:
776: public TrivialServiceProvider(Object svc) {
777: this .svc = svc;
778: }
779:
780: public Object getService(ServiceBroker sb, Object requestor,
781: Class serviceClass) {
782: return svc;
783: }
784:
785: public void releaseService(ServiceBroker sb, Object requestor,
786: Class serviceClass, Object service) {
787: }
788: }
789:
790: /**
791: * A minimal {@link Community} implementation, required because the
792: * {@link CommunityChangeEvent} constructor calls "community.getName()".
793: */
794: private static final class CommunityImpl implements Community {
795: private final String name;
796:
797: public CommunityImpl(String name) {
798: this .name = name;
799: }
800:
801: public String getName() {
802: return name;
803: }
804:
805: // the rest are not supported:
806: public void setName(String name) {
807: die();
808: }
809:
810: public void setAttributes(Attributes attrs) {
811: die();
812: }
813:
814: public Attributes getAttributes() {
815: die();
816: return null;
817: }
818:
819: public String toXml() {
820: die();
821: return null;
822: }
823:
824: public String toXml(String indent) {
825: die();
826: return null;
827: }
828:
829: public String attrsToString() {
830: die();
831: return null;
832: }
833:
834: //
835: public Collection getEntities() {
836: die();
837: return null;
838: }
839:
840: public Entity getEntity(String name) {
841: die();
842: return null;
843: }
844:
845: public boolean hasEntity(String name) {
846: die();
847: return false;
848: }
849:
850: public void addEntity(Entity entity) {
851: die();
852: }
853:
854: public void removeEntity(String entityName) {
855: die();
856: }
857:
858: public Set search(String filter, int qualifier) {
859: die();
860: return null;
861: }
862:
863: public String qualifierToString(int qualifier) {
864: die();
865: return null;
866: }
867:
868: //
869: private void die() {
870: throw new UnsupportedOperationException();
871: }
872: }
873:
874: private static final class CommunityResponseImpl implements
875: CommunityResponse {
876: private final boolean isSuccess;
877: private final String community;
878:
879: public CommunityResponseImpl(boolean isSuccess, String community) {
880: this .isSuccess = isSuccess;
881: this .community = community;
882: }
883:
884: public int getStatus() {
885: return (isSuccess ? SUCCESS : FAIL);
886: }
887:
888: public String getStatusAsString() {
889: return (isSuccess ? "SUCCESS" : "FAIL");
890: }
891:
892: public Object getContent() {
893: return new CommunityImpl(community);
894: }
895: }
896:
897: /**
898: * This is a paraniod wrapper around the listener callback, to make
899: * sure that "getCommunityName()" is well behaved.
900: */
901: private static final class ListenerImpl implements
902: CommunityChangeListener {
903: private final String s;
904: private final CommunityChangeListener l;
905:
906: public ListenerImpl(CommunityChangeListener l) {
907: this .l = l;
908: this .s = fix(l.getCommunityName());
909: }
910:
911: public String getCommunityName() {
912: return s;
913: }
914:
915: public void communityChanged(CommunityChangeEvent event) {
916: l.communityChanged(event);
917: }
918:
919: public int hashCode() {
920: return System.identityHashCode(l);
921: }
922:
923: public boolean equals(Object o) {
924: if (o == this )
925: return true;
926: if (!(o instanceof ListenerImpl))
927: return false;
928: return l == ((ListenerImpl) o).l;
929: }
930:
931: public String toString() {
932: return "(listener for community "
933: + (s.length() == 0 ? "*" : s) + ")";
934: }
935: }
936: }
|