001: /*
002: * <copyright>
003: *
004: * Copyright 2003-2004 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: package org.cougaar.lib.aggagent.session;
027:
028: import java.util.Collection;
029: import java.util.HashSet;
030: import java.util.Set;
031:
032: import org.cougaar.core.blackboard.IncrementalSubscription;
033: import org.cougaar.core.service.BlackboardService;
034: import org.cougaar.util.UnaryPredicate;
035:
036: /**
037: * <p>
038: * A RemoteBlackboardSubscription is a mechanism that allows remote clients to
039: * collect information from a COUGAAR Agent in much the same way as one of its
040: * Plugins. Instances of this class behave like an IncrementalSubscription,
041: * that is, they accumulate and incrementally report lists of blackboard
042: * objects that have been added, removed, or modified. However, their
043: * reporting model is not tied directly to the Cluster's event thread.
044: * </p><p>
045: * Each RemoteBlackboardSubscription is backed by an IncrementalSubscription,
046: * which actually gets information from the blackboard.
047: * </p><p>
048: * Access to incremental information is granted during a reporting
049: * transaction, which is started by calling open() and ended by calling
050: * close(). An IllegalStateException may be raised if the expected protocol
051: * is not followed.
052: * </p>
053: */
054: public class RemoteBlackboardSubscription implements SubscriptionAccess {
055: private Object lock = new Object();
056: private boolean transientQuery = false;
057: private boolean opened = false;
058: private boolean hasNewStuff = false;
059: private boolean dead = false;
060:
061: protected IncrementalSubscription subs = null;
062: protected BlackboardService bbs = null;
063:
064: private Set added = null;
065: private Set changed = null;
066: private Set removed = null;
067:
068: private Set newAdds = new HashSet();
069: private Set newChanges = new HashSet();
070: private Set newRemoves = new HashSet();
071:
072: /**
073: * Create a new RemoteBlackboardSubscription specifying no initialization
074: * parameters (no IncrementalSubscription is created). This may be used by
075: * subclasses to circumvent the default usage of the BlackboardService.
076: */
077: protected RemoteBlackboardSubscription() {
078: }
079:
080: /**
081: * Create a new RemoteBlackboardSubscription to gather Objects matching the
082: * given predicate. The BlackboardService reference allows the underlying
083: * IncrementalSubscription to be created.
084: */
085: public RemoteBlackboardSubscription(BlackboardService s,
086: UnaryPredicate p) {
087: this (s, p, false);
088: }
089:
090: /**
091: * Used for transient queries. Fill added collection with the results
092: * of a one-time query.
093: */
094: public RemoteBlackboardSubscription(BlackboardService s,
095: UnaryPredicate p, boolean transientQuery) {
096: this .transientQuery = transientQuery;
097: bbs = s;
098:
099: subs = (IncrementalSubscription) bbs.subscribe(p);
100: add(subs.getCollection());
101: }
102:
103: /**
104: * Unsubscribe from the Cluster's blackboard and destroy the underlying
105: * subscription. After this method is called, the RemoteSubscription is no
106: * longer operational (all of the client interface methods will throw
107: * IllegalStateException).
108: */
109: public void shutDown() {
110: synchronized (lock) {
111: unsubscribe();
112: subs = null;
113: dead = true;
114: }
115: }
116:
117: /**
118: * Destroy the underlying subscription. Subclasses, which may perform the
119: * operation differently, can do so by overriding this method.
120: */
121: protected void unsubscribe() {
122: if (bbs != null)
123: bbs.unsubscribe(subs);
124: }
125:
126: private void checkDead(String s) {
127: if (dead)
128: throw new IllegalStateException("operation " + s
129: + " is illegal after shutDown");
130: }
131:
132: private void checkOpened(String s) {
133: if (opened)
134: throw new IllegalStateException("operation " + s
135: + " is illegal -- a transaction is in progress");
136: }
137:
138: private void checkClosed(String s) {
139: if (!opened)
140: throw new IllegalStateException("operation " + s
141: + " is illegal -- must open a transaction first");
142: }
143:
144: /**
145: * Tell whether unreported changes to the subscription have been posted.
146: * This operation is not legal during reporting transactions.
147: */
148: public boolean hasChanged() {
149: synchronized (lock) {
150: checkDead("hasChanged");
151: checkOpened("hasChanged");
152: return hasNewStuff;
153: }
154: }
155:
156: /**
157: * Begin a reporting transaction. Lists of added, changed, and removed
158: * blackboard objects are constructed and held constant until the end of
159: * the transaction, as marked by a call to the close method. An
160: * IllegalStateException is raised if this method is called while a
161: * transaction is already in progress. Clients attempting to open a
162: * reporting transaction should catch this Exception and refrain from any
163: * calls to methods "getAddedCollection", "getChangedCollection",
164: * "getRemovedCollection", and "close" until such a time as the open method
165: * is allowed to succeed.
166: */
167: public void open() {
168: synchronized (lock) {
169: checkDead("open");
170: checkOpened("open");
171:
172: opened = true;
173: added = newAdds;
174: changed = newChanges;
175: removed = newRemoves;
176: newAdds = new HashSet();
177: newChanges = new HashSet();
178: newRemoves = new HashSet();
179: hasNewStuff = false;
180: }
181: }
182:
183: /**
184: * End a reporting transaction. Lists of added, changed, and removed
185: * blackboard objects are flushed so that they may be refilled when a new
186: * transaction is started by the open method. Calls to this method while
187: * there is no transaction in progress have no effect.
188: */
189: public void close() {
190: synchronized (lock) {
191: opened = false;
192: added = null;
193: changed = null;
194: removed = null;
195: }
196: }
197:
198: /**
199: * This method is legal only during a reporting transaction; calling it at
200: * another time raises an IllegalStateException. A Collection view of the
201: * list of blackboard Objects added since the start of the last transaction
202: * is returned.
203: */
204: public Collection getAddedCollection() {
205: synchronized (lock) {
206: checkDead("getAddedCollection");
207: checkClosed("getAddedCollection");
208: return new HashSet(added);
209: }
210: }
211:
212: /**
213: * This method is legal only during a reporting transaction; calling it at
214: * another time raises an IllegalStateException. A Collection view of the
215: * list of blackboard Objects changed since the start of the last
216: * transaction is returned.
217: */
218: public Collection getChangedCollection() {
219: synchronized (lock) {
220: checkDead("getChangedCollection");
221: checkClosed("getChangedCollection");
222: return new HashSet(changed);
223: }
224: }
225:
226: /**
227: * This method is legal only during a reporting transaction; calling it at
228: * another time raises an IllegalStateException. A Collection view of the
229: * list of blackboard Objects removed since the start of the last
230: * transaction is returned.
231: */
232: public Collection getRemovedCollection() {
233: synchronized (lock) {
234: checkDead("getRemovedCollection");
235: checkClosed("getRemovedCollection");
236: return new HashSet(removed);
237: }
238: }
239:
240: /**
241: * Obtain a Collection view of all blackboard Objects matching the predicate
242: * of this RemoteSubscription. The underlying IncrementalSubscription is
243: * queried for its contents, which are not necessarily synchronized with the
244: * reporting model maintained by this class. Calling this method is legal
245: * both inside and outside of reporting transactions.
246: */
247: public Collection getMembership() {
248: checkDead("getMembership");
249: if (transientQuery)
250: return new HashSet(added);
251: return new HashSet(subs);
252: }
253:
254: private void add(Collection c) {
255: synchronized (lock) {
256: newAdds.addAll(c);
257: newRemoves.removeAll(c);
258: newChanges.removeAll(c);
259: }
260: }
261:
262: private void change(Collection c) {
263: synchronized (lock) {
264: newChanges.addAll(c);
265: newChanges.removeAll(newAdds);
266: newChanges.removeAll(newRemoves);
267: }
268: }
269:
270: private void remove(Collection c) {
271: synchronized (lock) {
272: newRemoves.addAll(c);
273: newAdds.removeAll(c);
274: newChanges.removeAll(c);
275: }
276: }
277:
278: /**
279: * <p>
280: * Implementation of the UISubscriber interface. This method is called by
281: * the Cluster when it has posted updates to the Subscription underlying
282: * this RemoteSubscription. The Subscription argument required by the
283: * interface is ignored, and it is assumed that the changes are relevant to
284: * the Subscription managed locally.
285: * </p><p>
286: * If this method should happen to be called after shutDown, it is ignored.
287: * </p>
288: */
289: public void subscriptionChanged() {
290: synchronized (lock) {
291: if (dead) {
292: System.out
293: .println("RemoteSubscription::subscriptionChanged: ignored (never appear).");
294: } else {
295: add(subs.getAddedCollection());
296: change(subs.getChangedCollection());
297: remove(subs.getRemovedCollection());
298: hasNewStuff = true;
299: }
300: }
301: }
302:
303: public IncrementalSubscription getSubscription() {
304: return subs;
305: }
306: }
|