001: /*
002:
003: * <copyright>
004: *
005: * Copyright 2002-2007 BBNT Solutions, LLC
006: * under sponsorship of the Defense Advanced Research Projects
007: * Agency (DARPA).
008: *
009: * You can redistribute this software and/or modify it under the
010: * terms of the Cougaar Open Source License as published on the
011: * Cougaar Open Source Website (www.cougaar.org).
012: *
013: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
014: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
015: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
016: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
017: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
018: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
019: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
020: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
021: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
022: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
023: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
024: *
025: * </copyright>
026:
027: */
028:
029: package org.cougaar.qos.qrs;
030:
031: import org.cougaar.qos.ResourceStatus.ResourceNode;
032: import org.cougaar.util.log.Logger;
033:
034: import java.util.Observable;
035: import java.util.Observer;
036:
037: public class BoundDataFormula extends Observable implements
038: DataValueChangedCallbackListener {
039: private Object currentValue;
040:
041: private ResourceContext context;
042: private String symbol;
043:
044: private ResourceNode[] description;
045: private DataFormula formula;
046: private Runnable poller;
047: private boolean isReady;
048: private NotificationQualifier qualifier;
049:
050: /**
051: * Make a forward-chained binding of a context and a symbol in that context.
052: * If keepCurrent is true, a thread will be created to retrieve the value
053: * periodically. If keepCurrent is false, the value will only be retrieved
054: * on request.
055: */
056: public BoundDataFormula(ResourceContext context, String symbol,
057: boolean keepCurrent) {
058: this .symbol = symbol;
059: this .context = context;
060: isReady = true;
061: if (keepCurrent) {
062: update();
063: poller = new Runnable() {
064: public void run() {
065: update();
066: }
067: };
068: RSSUtils.schedule(poller, 0, 1000);
069: }
070: }
071:
072: /**
073: * Make a backward-chained binding of a formula. It will be notified when
074: * the formula's value changes.
075: */
076: public BoundDataFormula(DataFormula formula)
077: throws NullFormulaException {
078: if (formula == null) {
079: throw new NullFormulaException();
080: }
081: this .formula = formula;
082: isReady = true;
083: formula.subscribe(this );
084: // Initialize the value
085: dataValueChanged(formula);
086: }
087:
088: public BoundDataFormula(ResourceNode[] description)
089: throws NullFormulaException {
090: this (description, false, null);
091: }
092:
093: // NB: The caller is responsible for running the formula creator
094: // Runnable if delayed == true!
095: public BoundDataFormula(ResourceNode[] description,
096: boolean delayed, NotificationQualifier qualifier)
097: throws NullFormulaException {
098: this .description = description;
099: this .qualifier = qualifier;
100: if (!delayed) {
101: DataFormula f = RSS.instance().getPathFormula(description);
102: // We now have the formula but haven't subscribed yet. If
103: // it's deleted before we subscribe, we lose.
104:
105: if (f == null) {
106: throw new NullFormulaException();
107: }
108: setFormula(f);
109: } else {
110: // we need a valid initial value
111: currentValue = DataValue.NO_VALUE;
112: }
113: }
114:
115: void unsubscribe() {
116: if (formula != null) {
117: formula.unsubscribe(this );
118: // formula = null ?
119: }
120: }
121:
122: public boolean shouldNotify(DataValue value) {
123: Logger logger = Logging.getLogger(BoundDataFormula.class);
124: boolean result = qualifier == null
125: || qualifier.shouldNotify(value);
126: if (!result && logger.isInfoEnabled()) {
127: logger.info(value + " failed to satisfy " + qualifier);
128: }
129: return result;
130: }
131:
132: public Runnable getDelayedFormulaCreator() {
133: return new Runnable() {
134: public void run() {
135: // We now have the formula but haven't subscribed yet. If
136: // it's deleted before we subscribe, we lose.
137: setFormula(RSS.instance().getPathFormula(description));
138: }
139: };
140: }
141:
142: private void setFormula(DataFormula formula) {
143: if (isReady) {
144: return;
145: }
146: this .formula = formula;
147: isReady = true;
148: formula.subscribe(this );
149: // Initialize the value
150: initialNotify(formula);
151: }
152:
153: public void formulaDeleted(DataFormula formula) {
154: DataFormula replacement = null;
155: replacement = RSS.instance().getPathFormula(description);
156: // We now have the formula but haven't subscribed yet. If
157: // it's deleted before we subscribe, we lose.
158:
159: Logger logger = Logging.getLogger(BoundDataFormula.class);
160: if (logger.isInfoEnabled()) {
161: logger.info("Replaced deleted formula " + formula
162: + " with " + replacement);
163: }
164: this .formula = replacement;
165: isReady = true;
166: replacement.subscribe(this );
167: initialNotify(replacement);
168: }
169:
170: private void notifyObserversAndLog(Object value) {
171: Logger logger = Logging.getEventLogger(BoundDataFormula.class);
172: if (logger.isInfoEnabled()) {
173: StringBuffer buf = new StringBuffer();
174: buf.append("Callback: ");
175: buf.append(currentValue);
176: if (description != null) {
177: buf.append(" for ");
178: RSSUtils.pathToString(buf, description);
179: }
180: logger.info(buf.toString());
181: }
182: notifyObservers(value);
183: }
184:
185: private void update() {
186: Object raw = context.getValue(symbol);
187: setChanged();
188: if (raw instanceof DataValue) {
189: currentValue = raw;
190: notifyObserversAndLog(currentValue);
191: } else if (raw instanceof Number) {
192: currentValue = new Double(raw.toString());
193: notifyObserversAndLog(currentValue);
194: } else {
195: Logger logger = Logging.getLogger(BoundDataFormula.class);
196: logger
197: .error("Symbol " + symbol + " of " + context
198: + " has value " + raw
199: + " which is not a DataValue");
200: }
201: clearChanged();
202: }
203:
204: /**
205: * Callback from the DataFormula indicating that the value has changed. This
206: * implements the DataValueChangedCallbackListener interface.
207: */
208: public void dataValueChanged(DataFormula formula) {
209: DataValue rawValue = formula.query();
210: if (rawValue != null) {
211: currentValue = rawValue;
212: setChanged();
213: notifyObserversAndLog(currentValue);
214: clearChanged();
215: }
216: }
217:
218: private void initialNotify(DataFormula formula) {
219: DataValue rawValue = formula.query();
220: if (rawValue != null && !rawValue.equals(DataValue.NO_VALUE)) {
221: currentValue = rawValue;
222: if (shouldNotify(rawValue)) {
223: setChanged();
224: notifyObserversAndLog(currentValue);
225: clearChanged();
226: }
227: }
228: }
229:
230: public void addObserver(Observer o) {
231: super .addObserver(o);
232: DataValue current = (DataValue) getCurrentValue();
233: if (!current.equals(DataValue.NO_VALUE)
234: && shouldNotify(current)) {
235: o.update(this , current);
236: }
237: }
238:
239: public ResourceNode[] getDescription() {
240: return description;
241: }
242:
243: /**
244: * Return the current value, updating if necessary
245: */
246: public Object getCurrentValue() {
247: if (context != null && poller == null) {
248: // passive forward-chaining -- update manually on request
249: update();
250: }
251: return currentValue;
252: }
253:
254: }
|