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 java.util.ArrayList;
032: import java.util.HashMap;
033: import java.util.Iterator;
034: import java.util.List;
035: import java.util.Map;
036:
037: import org.cougaar.qos.ResourceStatus.ResourceNode;
038: import org.cougaar.util.log.Logger;
039:
040: /**
041: * Abstract definition of a contect for resource data, ie a collection of
042: * attributes bound over a set of parameter values. The attributes are
043: * implemented as DataFormula instances.
044: */
045: abstract public class ResourceContext implements Constants {
046: private static Map<String, ContextInstantiater> ContextInstantiaters = new HashMap<String, ContextInstantiater>();
047:
048: private ResourceContext parent;
049: private final Map<String, Map<Object, ResourceContext>> children;
050: private final Map<String, List<DataFormula>> formulas;
051: private final Map<String, Object> symbol_table;
052: private final String[] parameters;
053: private Object id;
054: private String kind;
055: private String pretty_name;
056:
057: /**
058: * Standard constructor. It verifies the parameters and adds the context to
059: * the list of known contexts.
060: */
061: protected ResourceContext(String[] parameters,
062: ResourceContext parent) throws ParameterError {
063: this .parameters = parameters;
064: this .parent = parent;
065: children = new HashMap<String, Map<Object, ResourceContext>>();
066: formulas = new HashMap<String, List<DataFormula>>();
067: symbol_table = new HashMap<String, Object>();
068: verifyParameters(parameters);
069: }
070:
071: /**
072: * Subclasses should provide this method. It should verify that the given
073: * parameters are correct in number, position, type and value, throwing
074: * ParameterError if not.
075: */
076: abstract protected void verifyParameters(String[] parameters)
077: throws ParameterError;
078:
079: abstract protected DataFormula instantiateFormula(
080: String formula_kind);
081:
082: protected void postInitialize() {
083: pretty_name = kind;
084: if (parameters != null && parameters.length > 0) {
085: pretty_name += "[" + parameters[0];
086: for (int i = 1; i < parameters.length; i++) {
087: pretty_name += ", " + parameters[i];
088: }
089: pretty_name += "]";
090: }
091: }
092:
093: public String toString() {
094: return pretty_name;
095: }
096:
097: protected List<ResourceNode> getPath() {
098: List<ResourceNode> path = null;
099: if (parent == null || parent == RSS.instance()
100: || !useParentPath()) {
101: path = new ArrayList<ResourceNode>();
102: } else {
103: path = parent.getPath();
104: }
105: path.add(new ResourceNode(kind, parameters));
106: return path;
107: }
108:
109: protected Object getID() {
110: return id;
111: }
112:
113: protected void setParent(ResourceContext parent) {
114: this .parent = parent;
115: }
116:
117: public Object getSymbolValue(String symbol) {
118: return symbol_table.get(symbol);
119: }
120:
121: protected void setSymbolValue(String symbol, Object value) {
122: if (symbol_table.containsKey(symbol)) {
123: symbol_table.put(symbol, value);
124: } else if (parent != null) {
125: parent.setSymbolValue(symbol, value);
126: } else {
127: Logger logger = Logging.getLogger(ResourceContext.class);
128: logger.error("### Attempt to set value of symbol " + symbol
129: + " which has no binding");
130: }
131: }
132:
133: public void bindSymbolValue(String symbol, Object value) {
134: symbol_table.put(symbol, value);
135: }
136:
137: protected void unbindSymbolValue(String symbol) {
138: if (symbol_table.containsKey(symbol)) {
139: symbol_table.remove(symbol);
140: } else {
141: Logger logger = Logging.getLogger(ResourceContext.class);
142: logger.error("### No binding for symbol " + symbol);
143: }
144: }
145:
146: protected void invoke(String method, String[] parameters)
147: throws NoSuchMethodException {
148: throw new NoSuchMethodException(method, parameters);
149: }
150:
151: // No-op by default. Subclasses should provide the
152: // context-specific interpretation of the dependencies.
153: protected void setDependencies(ResourceNode[] dependencies) {
154: }
155:
156: // private DataFormula instantiateFormula(String formula_kind)
157: // {
158: // ContextInstantiater instantiater = null;
159: // synchronized (ContextInstantiaters) {
160: // instantiater = (ContextInstantiater)
161: // ContextInstantiaters.get(kind);
162: // }
163: // if (instantiater != null)
164: // return instantiater.instantiateFormula(formula_kind);
165: // else
166: // return null;
167: // }
168:
169: private ResourceContext instantiateContext(String kind,
170: String[] parameters) {
171: ContextInstantiater instantiater = null;
172: synchronized (ContextInstantiaters) {
173: instantiater = ContextInstantiaters.get(kind);
174: }
175: if (instantiater != null) {
176: try {
177: return instantiater
178: .instantiateContext(parameters, this );
179: } catch (ParameterError error) {
180: Logger logger = Logging
181: .getLogger(ResourceContext.class);
182: logger.error(null, error);
183: return null;
184: }
185: } else {
186: Logger logger = Logging.getLogger(ResourceContext.class);
187: logger.error(kind + " is not a known ResourceContext");
188: // Thread.dumpStack();
189: return null;
190: }
191: }
192:
193: Object getIdentifier(String kind, String[] parameters) {
194: ContextInstantiater instantiater = null;
195: synchronized (ContextInstantiaters) {
196: instantiater = ContextInstantiaters.get(kind);
197: }
198: if (instantiater != null) {
199: return instantiater.identifyParameters(parameters);
200: } else {
201: return null;
202: }
203: }
204:
205: /**
206: * Returns a list of the classes of any ResourceContexts that have been
207: * instantiated so far. This is only used by the ResourceContext gui.
208: */
209: public List<String> getContextClasses() {
210: synchronized (children) {
211: List<String> classes = new ArrayList<String>();
212: classes.addAll(children.keySet());
213: return classes;
214: }
215: }
216:
217: /**
218: * Returns a list of the ResourceContexts for a given class that have been
219: * instantiated so far. This is only used by the ResourceContext gui.
220: */
221: public List<ResourceContext> getContextsForClass(String c) {
222: List<ResourceContext> result = new ArrayList<ResourceContext>();
223: synchronized (children) {
224: Map<Object, ResourceContext> context_map = children.get(c);
225: result.addAll(context_map.values());
226: }
227: return result;
228: }
229:
230: protected String getContextKind() {
231: return kind;
232: }
233:
234: protected boolean useParentPath() {
235: return true;
236: }
237:
238: // This method is a hook which allows ResourceContexts that are not at
239: // the root of the KB to appear as the first element in a path.
240: // In that case, the ResourceContext is resposible for finding or
241: // constructing the context in which it belongs. ResourceContexts which
242: // should never be the first element in a path should return null.
243: protected ResourceContext preferredParent(RSS root) {
244: return null;
245: }
246:
247: protected void removeChild(ResourceContext child) {
248: String kind = child.getContextKind();
249: synchronized (children) {
250: Map<Object, ResourceContext> kids = children.get(kind);
251: if (kids != null) {
252: kids.remove(child.id);
253: }
254: }
255: }
256:
257: protected void addChild(Object id, ResourceContext child) {
258: String kind = child.getContextKind();
259: synchronized (children) {
260: Map<Object, ResourceContext> kids = children.get(kind);
261: if (kids == null) {
262: kids = new HashMap<Object, ResourceContext>();
263: children.put(kind, kids);
264: }
265: kids.put(id, child);
266: }
267: }
268:
269: protected ResourceContext resolveSpec(String kind,
270: String[] parameters) {
271: Object id = getIdentifier(kind, parameters);
272: return resolveSpec(id, kind, parameters);
273: }
274:
275: ResourceContext resolveSpec(Object id, String kind,
276: String[] parameters) {
277: synchronized (children) {
278: Map<Object, ResourceContext> kids = children.get(kind);
279: if (kids != null) {
280: ResourceContext context = kids.get(id);
281: if (context != null) {
282: return context;
283: }
284: }
285:
286: // include parent as argument
287: ResourceContext context = instantiateContext(kind,
288: parameters);
289: if (context == null) {
290: return null;
291: } else {
292: // cache in children hashtable
293: context.id = id;
294: context.kind = kind;
295: context.postInitialize();
296: addChild(id, context);
297: RSS.instance().eventNotification(context,
298: RSS.Event.CREATION_EVENT);
299: // callback hook
300: return context;
301: }
302: }
303: }
304:
305: /**
306: * ResourceContext factory method, arbitrary depth.
307: */
308: public DataFormula getPathFormula(ResourceNode[] path) {
309: Logger logger = Logging.getLogger(ResourceContext.class);
310: if (path == null) {
311: if (logger.isDebugEnabled()) {
312: logger.debug("Path is null");
313: }
314: return null;
315: }
316:
317: ResourceContext context = this ;
318: ResourceNode node;
319: // The last item is the formula
320: for (int i = 0; i < path.length - 1; i++) {
321: node = path[i];
322: context = context.resolveSpec(node.kind, node.parameters);
323: if (logger.isDebugEnabled()) {
324: logger.debug("context " + context);
325: }
326: if (context == null) {
327: return null;
328: }
329: }
330: node = path[path.length - 1];
331:
332: DataFormula result = context.getFormula(node.kind,
333: node.parameters);
334: if (logger.isDebugEnabled()) {
335: logger.debug("Formula " + result);
336: }
337: return result;
338: }
339:
340: public ResourceContext getPathContext(ResourceNode[] path) {
341: if (path == null) {
342: return null;
343: }
344: ResourceContext context = this ;
345: ResourceNode node;
346: // The last item is the formula
347: for (ResourceNode element : path) {
348: node = element;
349: context = context.resolveSpec(node.kind, node.parameters);
350: if (context == null) {
351: return null;
352: }
353: }
354: return context;
355: }
356:
357: protected Object nextLookup(String symbol, String[] args,
358: boolean resolve) {
359: if (parent != null) {
360: return parent.lookupSymbol(symbol, args, resolve);
361: } else {
362: return null;
363: }
364: }
365:
366: protected Object lookupSymbol(String symbol, String[] args,
367: boolean resolve) {
368: Logger logger = Logging.getLogger(ResourceContext.class);
369: if (resolve) {
370: // Check parameter bindings first if we're resolving.
371: Object value = getSymbolValue(symbol);
372: // if (logger.isInfoEnabled())
373: // logger.info(this +" got "+ value +" for " +symbol);
374: if (value != null) {
375: return value;
376: }
377: }
378: // No parameter binding (or we're not resolving). Look for a
379: // local formula.
380: DataFormula formula = findOrMakeLocalFormula(symbol, args);
381: if (formula == null) {
382: Object value = nextLookup(symbol, args, resolve);
383: if (!resolve) {
384: DataFormula parentFormula = (DataFormula) value;
385: if (parentFormula == null) {
386: return null;
387: } else {
388: // Make a local proxy and return that
389: return makeProxyFormula(parentFormula, symbol, args);
390: }
391: } else {
392: return value;
393: }
394: } else if (resolve) {
395: return formula.query();
396: } else {
397: if (logger.isInfoEnabled()) {
398: logger
399: .info(this + " got " + formula + " for "
400: + symbol);
401: }
402: return formula;
403: }
404: }
405:
406: public Object getValue(String symbol) {
407: return lookupSymbol(symbol, null, true);
408: }
409:
410: public DataFormula getFormula(String kind, String[] args) {
411: return (DataFormula) lookupSymbol(kind, args, false);
412: }
413:
414: /**
415: * Return the parent of this.
416: */
417: public ResourceContext getParent() {
418: return parent;
419: }
420:
421: /**
422: * Returns an list of Formula classes instantiated so far by this context.
423: * Only used by the ResourceContext gui.
424: */
425: public List<String> getFormulaKinds() {
426: synchronized (formulas) {
427: List<String> kinds = new ArrayList<String>();
428: kinds.addAll(formulas.keySet());
429: return kinds;
430: }
431: }
432:
433: /**
434: * Returns a list of the Formulas for a given class that have been
435: * instantiated so far. This is only used by the ResourceContext gui.
436: */
437: public List<DataFormula> getFormulasForKind(String kind) {
438: synchronized (formulas) {
439: return formulas.get(kind);
440: }
441: }
442:
443: /**
444: * Returns a list of the parameter bindings. So far this is only used by the
445: * ResourceContext gui.
446: */
447: public String[] getParameters() {
448: return parameters;
449: }
450:
451: protected boolean paramEqual(Object x, Object y) {
452: return x.equals(y);
453: }
454:
455: private DataFormula makeFormula(String formula_name,
456: String[] formula_args) {
457: DataFormula formula = instantiateFormula(formula_name);
458: if (formula != null) {
459: initializeFormula(formula, formula_name, formula_args);
460: return formula;
461: } else {
462: Logger logger = Logging.getLogger(ResourceContext.class);
463: if (logger.isInfoEnabled()) {
464: logger.info(this + " failed to lookup " + formula_name);
465: }
466: return null;
467: }
468: }
469:
470: private void initializeFormula(DataFormula formula, String name,
471: String[] args) {
472: formula.setName(name);
473: formula.setArgs(args);
474: formula.initialize(this );
475: formula.postInitialize();
476: }
477:
478: /**
479: * Returns a formula from the cache for the given name, or makes one (and
480: * caches it) if none is cached. In the second case the 'makeFormula()'
481: * method is used to create a new DataFormula.
482: */
483: private DataFormula findOrMakeLocalFormula(String name,
484: String[] args) {
485: synchronized (formulas) {
486: DataFormula formula = null;
487: List<DataFormula> forms = formulas.get(name);
488: if (forms == null) {
489: forms = new ArrayList<DataFormula>();
490: formulas.put(name, forms);
491: } else {
492:
493: for (DataFormula candidate : forms) {
494: if (candidate.hasArgs(args)) {
495: return candidate;
496: }
497: }
498: }
499:
500: // No extant formula
501: formula = makeFormula(name, args);
502: if (formula != null) {
503: forms.add(formula);
504: }
505:
506: return formula;
507: }
508: }
509:
510: private DataFormula makeProxyFormula(DataFormula formula,
511: String name, String[] args) {
512: Logger logger = Logging.getLogger(ResourceContext.class);
513: if (formula == null) {
514: logger.error("### Null delegate for " + name);
515: return null;
516: } else {
517: synchronized (formulas) {
518: if (logger.isDebugEnabled()) {
519: logger.debug("Making proxy for " + formula
520: + " in context " + this );
521: }
522: ProxyFormula proxy = new ProxyFormula(formula);
523: initializeFormula(proxy, name, args);
524: List<DataFormula> forms = formulas.get(name);
525: forms.add(proxy);
526: return proxy;
527: }
528: }
529: }
530:
531: protected void reinitializeFormulas() {
532: synchronized (formulas) {
533: for (List<DataFormula> forms : formulas.values()) {
534: for (DataFormula formula : forms) {
535: formula.reinitialize();
536: }
537: }
538: }
539: }
540:
541: protected void delete() {
542: parent.removeChild(this );
543: Logger logger = Logging.getLogger(ResourceContext.class);
544:
545: // Delete child children
546: RSS rss = RSS.instance();
547: synchronized (children) {
548: for (Map<Object, ResourceContext> kids : children.values()) {
549: if (kids != null) {
550: Iterator<Map.Entry<Object, ResourceContext>> ctxt_itr = kids
551: .entrySet().iterator();
552: while (ctxt_itr.hasNext()) {
553: Map.Entry<Object, ResourceContext> child = ctxt_itr
554: .next();
555: ResourceContext child_ctxt = child.getValue();
556: ctxt_itr.remove();
557: rss.deleteContext(child_ctxt);
558: }
559: }
560: }
561: }
562:
563: // Delete formulas
564: synchronized (formulas) {
565: for (Map.Entry<String, List<DataFormula>> entry : formulas
566: .entrySet()) {
567: if (logger.isDebugEnabled()) {
568: Object key = entry.getKey();
569: logger.debug("Walking through " + key
570: + " formulas for deletion");
571: }
572: List<DataFormula> forms = entry.getValue();
573: if (forms != null) {
574: for (DataFormula formula : forms) {
575: if (logger.isDebugEnabled()) {
576: logger.debug(this
577: + " has informed formula "
578: + formula
579: + " that the context is deleted");
580: }
581: formula.contextDeleted();
582: }
583: }
584: }
585: }
586: }
587:
588: public static void registerContextInstantiater(String kind,
589: ContextInstantiater instantiater) {
590: synchronized (ContextInstantiaters) {
591: ContextInstantiaters.put(kind, instantiater);
592: }
593: }
594:
595: /**
596: * Exception class used to indicate the one of the supplied parameters is
597: * missing, has the wrong type or has an unacceptable value.
598: */
599: public static class ParameterError extends Exception {
600: public ParameterError(String message) {
601: super (message);
602: }
603: }
604:
605: public static class NoSuchMethodException extends Exception {
606: private final String method;
607: private final String[] args;
608:
609: public NoSuchMethodException(String method, String[] args) {
610: super ("No such Context method as " + method);
611: this .method = method;
612: this .args = args;
613: }
614:
615: public String getMethod() {
616: return method;
617: }
618:
619: public String[] getArgs() {
620: return args;
621: }
622: }
623: }
|