diff options
Diffstat (limited to 'qpid/extras/qmf/src/py/qmf2/common.py')
-rw-r--r-- | qpid/extras/qmf/src/py/qmf2/common.py | 1943 |
1 files changed, 1943 insertions, 0 deletions
diff --git a/qpid/extras/qmf/src/py/qmf2/common.py b/qpid/extras/qmf/src/py/qmf2/common.py new file mode 100644 index 0000000000..8107b86666 --- /dev/null +++ b/qpid/extras/qmf/src/py/qmf2/common.py @@ -0,0 +1,1943 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +import time +from logging import getLogger +from threading import Lock +from threading import Condition +try: + import hashlib + _md5Obj = hashlib.md5 +except ImportError: + import md5 + _md5Obj = md5.new + +log = getLogger("qmf") +log_query = getLogger("qmf.query") + + +## +## Constants +## + +AMQP_QMF_SUBJECT = "qmf" +AMQP_QMF_VERSION = 4 +AMQP_QMF_SUBJECT_FMT = "%s%d.%s" + +class MsgKey(object): + agent_info = "agent_info" + query = "query" + package_info = "package_info" + schema_id = "schema_id" + schema = "schema" + object_id="object_id" + data_obj="object" + method="method" + event="event" + + +class OpCode(object): + noop = "noop" + + # codes sent by a console and processed by the agent + agent_locate = "agent-locate" + cancel_subscription = "cancel-subscription" + create_subscription = "create-subscription" + get_query = "get-query" + method_req = "method" + renew_subscription = "renew-subscription" + schema_query = "schema-query" # @todo: deprecate + + # codes sent by the agent to a console + agent_ind = "agent" + data_ind = "data" + event_ind = "event" + managed_object = "managed-object" + object_ind = "object" + response = "response" + schema_ind="schema" # @todo: deprecate + + + + +def make_subject(_code): + """ + Create a message subject field value. + """ + return AMQP_QMF_SUBJECT_FMT % (AMQP_QMF_SUBJECT, AMQP_QMF_VERSION, _code) + + +def parse_subject(_sub): + """ + Deconstruct a subject field, return version,opcode values + """ + if _sub[:3] != "qmf": + raise Exception("Non-QMF message received") + + return _sub[3:].split('.', 1) + +def timedelta_to_secs(td): + """ + Convert a time delta to a time interval in seconds (float) + """ + return td.days * 86400 + td.seconds + td.microseconds/1000000.0 + + +##============================================================================== +## Async Event Model +##============================================================================== + + +class Notifier(object): + """ + Virtual base class that defines a call back which alerts the application that + a QMF Console notification is pending. + """ + def indication(self): + """ + Called when one or more items are ready for the application to process. + This method may be called by an internal QMF library thread. Its purpose is to + indicate that the application should process pending work items. + """ + raise Exception("The indication method must be overridden by the application!") + + + +class WorkItem(object): + """ + Describes an event that has arrived for the application to process. The + Notifier is invoked when one or more of these WorkItems become available + for processing. + """ + # Enumeration of the types of WorkItems produced on the Console + AGENT_ADDED=1 + AGENT_DELETED=2 + NEW_PACKAGE=3 + NEW_CLASS=4 + OBJECT_UPDATE=5 + EVENT_RECEIVED=7 + AGENT_HEARTBEAT=8 + QUERY_COMPLETE=9 + METHOD_RESPONSE=10 + # Enumeration of the types of WorkItems produced on the Agent + METHOD_CALL=1000 + QUERY=1001 + SUBSCRIBE=1002 + UNSUBSCRIBE=1003 + + def __init__(self, kind, handle, _params=None): + """ + Used by the Console to create a work item. + + @type kind: int + @param kind: work item type + """ + self._kind = kind + self._handle = handle + self._params = _params + + def get_type(self): + return self._kind + + def get_handle(self): + return self._handle + + def get_params(self): + return self._params + + + +##============================================================================== +## Addressing +##============================================================================== + +class QmfAddress(object): + """ + Address format: "qmf.<domain>.[topic|direct]/<subject>" + TBD + """ + + TYPE_DIRECT = "direct" + TYPE_TOPIC = "topic" + + ADDRESS_FMT = "qmf.%s.%s/%s" + DEFAULT_DOMAIN = "default" + + # Directly-addressed messages: + # agent's direct address: "qmf.<domain>.direct/<agent-name> + # console's direct address: "qmf.<domain>.direct/<console-name> + + # Well-known Topic Addresses: + # "qmf.<domain>.topic/<subject> + # Where <subject> has the following format: + # "console.ind#" - indications sent from consoles + # "agent.ind#" - indications sent from agents + # + # The following "well known" subjects are defined: + # + # console.ind.locate[.<agent-name>] - agent discovery request + # agent.ind.heartbeat[.<agent-name>"] - agent heartbeats + # agent.ind.event[.<severity>.<agent-name>] - events + # agent.ind.schema[TBD] - schema updates + # + SUBJECT_AGENT_IND="agent.ind" + SUBJECT_AGENT_HEARTBEAT = "agent.ind.heartbeat" + SUBJECT_AGENT_EVENT="agent.ind.event" + SUBJECT_AGENT_SCHEMA="agent.ind.schema" + + SUBJECT_CONSOLE_IND="console.ind" + SUBJECT_CONSOLE_LOCATE_AGENT="console.ind.locate" + + + + def __init__(self, subject, domain, type_): + if '/' in domain or '.' in domain: + raise Exception("domain string must not contain '/' or '.'" + " characters.") + + self._subject = subject + self._domain = domain + self._type = type_ + + def _direct(cls, subject, _domain=None): + if _domain is None: + _domain = QmfAddress.DEFAULT_DOMAIN + return cls(subject, _domain, type_=QmfAddress.TYPE_DIRECT) + direct = classmethod(_direct) + + def _topic(cls, subject, _domain=None): + if _domain is None: + _domain = QmfAddress.DEFAULT_DOMAIN + return cls(subject, _domain, type_=QmfAddress.TYPE_TOPIC) + topic = classmethod(_topic) + + def __from_string(cls, address): + node,subject = address.split('/',1) + qmf,domain,type_ = node.split('.',2) + + if qmf != "qmf" or (type_ != QmfAddress.TYPE_DIRECT and + type_ != QmfAddress.TYPE_TOPIC): + raise ValueError("invalid QmfAddress format: %s" % address) + + return cls(subject, domain, type_) + from_string = classmethod(__from_string) + + def get_address(self): + """ + Return the QMF address as a string, suitable for use with the AMQP + messaging API. + """ + return str(self) + + def get_node(self): + """ + Return the 'node' portion of the address. + """ + return self.get_address().split('/',1)[0] + + def get_subject(self): + """ + Return the 'subject' portion of the address. + """ + return self.get_address().split('/',1)[1] + + def get_domain(self): + return self._domain + + def is_direct(self): + return self._type == self.TYPE_DIRECT + + def __repr__(self): + return QmfAddress.ADDRESS_FMT % (self._domain, self._type, self._subject) + + + + +class AgentName(object): + """ + Uniquely identifies a management agent within the management domain. + """ + _separator = ":" + + def __init__(self, vendor, product, name, _str=None): + """ + Note: this object must be immutable, as it is used to index into a dictionary + """ + if _str is not None: + # construct from string representation + if _str.count(AgentName._separator) < 2: + raise TypeError("AgentName string format must be 'vendor.product.name'") + self._vendor, self._product, self._name = _str.split(AgentName._separator) + else: + self._vendor = vendor + self._product = product + self._name = name + + + def _from_str(cls, str_): + return cls(None, None, None, str_=str_) + from_str = classmethod(_from_str) + + def vendor(self): + return self._vendor + + def product(self): + return self._product + + def name(self): + return self._name + + def __cmp__(self, other): + if not isinstance(other, AgentName) : + raise TypeError("Invalid types for compare") + # return 1 + me = str(self) + them = str(other) + + if me < them: + return -1 + if me > them: + return 1 + return 0 + + def __hash__(self): + return (self._vendor, self._product, self._name).__hash__() + + def __repr__(self): + return self._vendor + AgentName._separator + \ + self._product + AgentName._separator + \ + self._name + + + +##============================================================================== +## DATA MODEL +##============================================================================== + + +class _mapEncoder(object): + """ + virtual base class for all objects that support being converted to a map + """ + + def map_encode(self): + raise Exception("The map_encode method my be overridden.") + + +class QmfData(_mapEncoder): + """ + Base class representing management data. + + Map format: + map["_values"] = map of unordered "name"=<value> pairs (optional) + map["_subtype"] = map of unordered "name"="subtype string" pairs (optional) + map["_tag"] = application-specific tag for this instance (optional) + """ + KEY_VALUES = "_values" + KEY_SUBTYPES = "_subtypes" + KEY_TAG="_tag" + KEY_OBJECT_ID = "_object_id" + KEY_SCHEMA_ID = "_schema_id" + KEY_UPDATE_TS = "_update_ts" + KEY_CREATE_TS = "_create_ts" + KEY_DELETE_TS = "_delete_ts" + + def __init__(self, + _values={}, _subtypes={}, _tag=None, + _object_id=None, _schema_id=None, + _ctime = 0, _utime = 0, _dtime = 0, + _map=None, _const=False): + """ + @type _values: dict + @param _values: dictionary of initial name=value pairs for object's + named data. + @type _subtypes: dict + @param _subtype: dictionary of subtype strings for each of the object's + named data. + @type _desc: string + @param _desc: Human-readable description of this data object. + @type _const: boolean + @param _const: if true, this object cannot be modified + """ + if _map is not None: + # construct from map + _tag = _map.get(self.KEY_TAG, _tag) + _values = _map.get(self.KEY_VALUES, _values) + _subtypes = _map.get(self.KEY_SUBTYPES, _subtypes) + _object_id = _map.get(self.KEY_OBJECT_ID, _object_id) + sid = _map.get(self.KEY_SCHEMA_ID) + if sid: + _schema_id = SchemaClassId.from_map(sid) + _ctime = long(_map.get(self.KEY_CREATE_TS, _ctime)) + _utime = long(_map.get(self.KEY_UPDATE_TS, _utime)) + _dtime = long(_map.get(self.KEY_DELETE_TS, _dtime)) + + if _object_id is None: + raise Exception("An object_id must be provided.") + + self._values = _values.copy() + self._subtypes = _subtypes.copy() + self._tag = _tag + self._ctime = _ctime + self._utime = _utime + self._dtime = _dtime + self._const = _const + self._schema_id = _schema_id + self._object_id = str(_object_id) + + + def __create(cls, values, _subtypes={}, _tag=None, _object_id=None, + _schema_id=None, _const=False): + # timestamp in millisec since epoch UTC + ctime = long(time.time() * 1000) + return cls(_values=values, _subtypes=_subtypes, _tag=_tag, + _ctime=ctime, _utime=ctime, + _object_id=_object_id, _schema_id=_schema_id, _const=_const) + create = classmethod(__create) + + def __from_map(cls, map_, _const=False): + return cls(_map=map_, _const=_const) + from_map = classmethod(__from_map) + + def is_managed(self): + return self._object_id is not None + + def is_described(self): + return self._schema_id is not None + + def get_tag(self): + return self._tag + + def get_value(self, name): + """ + Will throw an AttributeError exception if the named value does not exist. + """ + # meta-properties first: + if name == SchemaClassId.KEY_PACKAGE: + if self._schema_id: + return self._schema_id.get_package_name() + return None + if name == SchemaClassId.KEY_CLASS: + if self._schema_id: + return self._schema_id.get_class_name() + return None + if name == SchemaClassId.KEY_TYPE: + if self._schema_id: + return self._schema_id.get_type() + return None + if name == SchemaClassId.KEY_HASH: + if self._schema_id: + return self._schema_id.get_hash_string() + return None + if name == self.KEY_SCHEMA_ID: + return self._schema_id + if name == self.KEY_OBJECT_ID: + return self._object_id + if name == self.KEY_TAG: + return self._tag + if name == self.KEY_UPDATE_TS: + return self._utime + if name == self.KEY_CREATE_TS: + return self._ctime + if name == self.KEY_DELETE_TS: + return self._dtime + + try: + return self._values[name] + except KeyError: + raise AttributeError("no value named '%s' in this object" % name) + + def has_value(self, name): + + if name in [SchemaClassId.KEY_PACKAGE, SchemaClassId.KEY_CLASS, + SchemaClassId.KEY_TYPE, SchemaClassId.KEY_HASH, + self.KEY_SCHEMA_ID]: + return self._schema_id is not None + if name in [self.KEY_UPDATE_TS, self.KEY_CREATE_TS, + self.KEY_DELETE_TS]: + return True + if name == self.KEY_OBJECT_ID: + return self._object_id is not None + if name == self.KEY_TAG: + return self._tag is not None + + return name in self._values + + def set_value(self, _name, _value, _subType=None): + if self._const: + raise Exception("cannot modify constant data object") + self._values[_name] = _value + if _subType: + self._subtypes[_name] = _subType + return _value + + def get_subtype(self, _name): + return self._subtypes.get(_name) + + def get_schema_class_id(self): + """ + @rtype: class SchemaClassId + @returns: the identifier of the Schema that describes the structure of the data. + """ + return self._schema_id + + def get_object_id(self): + """ + Get the instance's identification string. + @rtype: str + @returns: the identification string, or None if not assigned and id. + """ + return self._object_id + + def map_encode(self): + _map = {} + if self._tag: + _map[self.KEY_TAG] = self._tag + + # data in the _values map may require recursive map_encode() + vmap = {} + for name,val in self._values.iteritems(): + if isinstance(val, _mapEncoder): + vmap[name] = val.map_encode() + else: + # otherwise, just toss in the native type... + vmap[name] = val + + _map[self.KEY_VALUES] = vmap + # subtypes are never complex, so safe to just copy + _map[self.KEY_SUBTYPES] = self._subtypes.copy() + if self._object_id: + _map[self.KEY_OBJECT_ID] = self._object_id + if self._schema_id: + _map[self.KEY_SCHEMA_ID] = self._schema_id.map_encode() + return _map + + def __repr__(self): + return "QmfData=<<" + str(self.map_encode()) + ">>" + + + def __setattr__(self, _name, _value): + # ignore private data members + if _name[0] == '_': + return super(QmfData, self).__setattr__(_name, _value) + if _name in self._values: + return self.set_value(_name, _value) + return super(QmfData, self).__setattr__(_name, _value) + + def __getattr__(self, _name): + if _name != "_values" and _name in self._values: + return self._values[_name] + raise AttributeError("no value named '%s' in this object" % _name) + + def __getitem__(self, _name): + return self.__getattr__(_name) + + def __setitem__(self, _name, _value): + return self.__setattr__(_name, _value) + + + +class QmfEvent(QmfData): + """ + A QMF Event is a type of described data that is not managed. Events are + notifications that are sent by Agents. An event notifies a Console of a + change in some aspect of the system under managment. + """ + KEY_TIMESTAMP = "_timestamp" + KEY_SEVERITY = "_severity" + + SEV_EMERG = "emerg" + SEV_ALERT = "alert" + SEV_CRIT = "crit" + SEV_ERR = "err" + SEV_WARNING = "warning" + SEV_NOTICE = "notice" + SEV_INFO = "info" + SEV_DEBUG = "debug" + + def __init__(self, _timestamp=None, _sev=SEV_NOTICE, _values={}, + _subtypes={}, _tag=None, + _map=None, + _schema_id=None, _const=True): + """ + @type _map: dict + @param _map: if not None, construct instance from map representation. + @type _timestamp: int + @param _timestamp: moment in time when event occurred, expressed + as milliseconds since Midnight, Jan 1, 1970 UTC. + @type _agentId: class AgentId + @param _agentId: Identifies agent issuing this event. + @type _schema: class Schema + @param _schema: + @type _schemaId: class SchemaClassId (event) + @param _schemaId: identi + """ + + if _map is not None: + # construct from map + super(QmfEvent, self).__init__(_map=_map, _const=_const, + _object_id="_event") + _timestamp = _map.get(self.KEY_TIMESTAMP, _timestamp) + _sev = _map.get(self.KEY_SEVERITY, _sev) + else: + super(QmfEvent, self).__init__(_object_id="_event", + _values=_values, + _subtypes=_subtypes, _tag=_tag, + _schema_id=_schema_id, + _const=_const) + if _timestamp is None: + raise TypeError("QmfEvent: a valid timestamp is required.") + + try: + self._timestamp = long(_timestamp) + except: + raise TypeError("QmfEvent: a numeric timestamp is required.") + + self._severity = _sev + + def _create(cls, timestamp, severity, values, + _subtypes={}, _tag=None, _schema_id=None, _const=False): + return cls(_timestamp=timestamp, _sev=severity, _values=values, + _subtypes=_subtypes, _tag=_tag, _schema_id=_schema_id, _const=_const) + create = classmethod(_create) + + def _from_map(cls, map_, _const=False): + return cls(_map=map_, _const=_const) + from_map = classmethod(_from_map) + + def get_timestamp(self): + return self._timestamp + + def get_severity(self): + return self._severity + + def map_encode(self): + _map = super(QmfEvent, self).map_encode() + _map[self.KEY_TIMESTAMP] = self._timestamp + _map[self.KEY_SEVERITY] = self._severity + return _map + + + + + +#============================================================================== +#============================================================================== +#============================================================================== + + + + +class Arguments(object): + def __init__(self, map): + pass +# self.map = map +# self._by_hash = {} +# key_count = self.map.keyCount() +# a = 0 +# while a < key_count: +# self._by_hash[self.map.key(a)] = self.by_key(self.map.key(a)) +# a += 1 + + +# def __getitem__(self, key): +# return self._by_hash[key] + + +# def __setitem__(self, key, value): +# self._by_hash[key] = value +# self.set(key, value) + + +# def __iter__(self): +# return self._by_hash.__iter__ + + +# def __getattr__(self, name): +# if name in self._by_hash: +# return self._by_hash[name] +# return super.__getattr__(self, name) + + +# def __setattr__(self, name, value): +# # +# # ignore local data members +# # +# if (name[0] == '_' or +# name == 'map'): +# return super.__setattr__(self, name, value) + +# if name in self._by_hash: +# self._by_hash[name] = value +# return self.set(name, value) + +# return super.__setattr__(self, name, value) + + +# def by_key(self, key): +# val = self.map.byKey(key) +# vType = val.getType() +# if vType == TYPE_UINT8: return val.asUint() +# elif vType == TYPE_UINT16: return val.asUint() +# elif vType == TYPE_UINT32: return val.asUint() +# elif vType == TYPE_UINT64: return val.asUint64() +# elif vType == TYPE_SSTR: return val.asString() +# elif vType == TYPE_LSTR: return val.asString() +# elif vType == TYPE_ABSTIME: return val.asInt64() +# elif vType == TYPE_DELTATIME: return val.asUint64() +# elif vType == TYPE_REF: return ObjectId(val.asObjectId()) +# elif vType == TYPE_BOOL: return val.asBool() +# elif vType == TYPE_FLOAT: return val.asFloat() +# elif vType == TYPE_DOUBLE: return val.asDouble() +# elif vType == TYPE_UUID: return val.asUuid() +# elif vType == TYPE_INT8: return val.asInt() +# elif vType == TYPE_INT16: return val.asInt() +# elif vType == TYPE_INT32: return val.asInt() +# elif vType == TYPE_INT64: return val.asInt64() +# else: +# # when TYPE_MAP +# # when TYPE_OBJECT +# # when TYPE_LIST +# # when TYPE_ARRAY +# logging.error( "Unsupported Type for Get? '%s'" % str(val.getType())) +# return None + + +# def set(self, key, value): +# val = self.map.byKey(key) +# vType = val.getType() +# if vType == TYPE_UINT8: return val.setUint(value) +# elif vType == TYPE_UINT16: return val.setUint(value) +# elif vType == TYPE_UINT32: return val.setUint(value) +# elif vType == TYPE_UINT64: return val.setUint64(value) +# elif vType == TYPE_SSTR: +# if value: +# return val.setString(value) +# else: +# return val.setString('') +# elif vType == TYPE_LSTR: +# if value: +# return val.setString(value) +# else: +# return val.setString('') +# elif vType == TYPE_ABSTIME: return val.setInt64(value) +# elif vType == TYPE_DELTATIME: return val.setUint64(value) +# elif vType == TYPE_REF: return val.setObjectId(value.impl) +# elif vType == TYPE_BOOL: return val.setBool(value) +# elif vType == TYPE_FLOAT: return val.setFloat(value) +# elif vType == TYPE_DOUBLE: return val.setDouble(value) +# elif vType == TYPE_UUID: return val.setUuid(value) +# elif vType == TYPE_INT8: return val.setInt(value) +# elif vType == TYPE_INT16: return val.setInt(value) +# elif vType == TYPE_INT32: return val.setInt(value) +# elif vType == TYPE_INT64: return val.setInt64(value) +# else: +# # when TYPE_MAP +# # when TYPE_OBJECT +# # when TYPE_LIST +# # when TYPE_ARRAY +# logging.error("Unsupported Type for Set? '%s'" % str(val.getType())) +# return None + + + +#class MethodResponse(object): +# def __init__(self, impl): +# pass +# self.impl = qmfengine.MethodResponse(impl) + + +# def status(self): +# return self.impl.getStatus() + + +# def exception(self): +# return self.impl.getException() + + +# def text(self): +# return exception().asString() + + +# def args(self): +# return Arguments(self.impl.getArgs()) + + +# def __getattr__(self, name): +# myArgs = self.args() +# return myArgs.__getattr__(name) + + +# def __setattr__(self, name, value): +# if name == 'impl': +# return super.__setattr__(self, name, value) + +# myArgs = self.args() +# return myArgs.__setattr__(name, value) + + + +# ##============================================================================== +# ## QUERY +# ##============================================================================== + + + +# def _doQuery(predicate, params ): +# """ +# Given the predicate from a query, and a map of named parameters, apply the predicate +# to the parameters, and return True or False. +# """ +# if type(predicate) != list or len(predicate) < 1: +# return False + +# elif opr == Query._LOGIC_AND: +# logging.debug("_doQuery() AND: [%s]" % predicate ) +# rc = False +# for exp in predicate[1:]: +# rc = _doQuery( exp, params ) +# if not rc: +# break +# return rc + +# elif opr == Query._LOGIC_OR: +# logging.debug("_doQuery() OR: [%s]" % predicate ) +# rc = False +# for exp in predicate[1:]: +# rc = _doQuery( exp, params ) +# if rc: +# break +# return rc + +# elif opr == Query._LOGIC_NOT: +# logging.debug("_doQuery() NOT: [%s]" % predicate ) +# if len(predicate) != 2: +# logging.warning("Malformed query not-expression received: '%s'" % predicate) +# return False +# return not _doQuery( predicate[1:], params ) + + + +# else: +# logging.warning("Unknown query operator received: '%s'" % opr) +# return False + + + +class QmfQuery(_mapEncoder): + + KEY_TARGET="what" + KEY_PREDICATE="where" + KEY_ID="id" + + ### Query Types + ID=1 + PREDICATE=2 + + #### Query Targets #### + TARGET_PACKAGES="schema_package" + # (returns just package names) + # allowed predicate key(s): + # + # SchemaClassId.KEY_PACKAGE + + TARGET_SCHEMA_ID="schema_id" + TARGET_SCHEMA="schema" + # allowed id: value: + # SchemaClassId + # + # allowed predicate key(s): + # SchemaClassId.KEY_PACKAGE + # SchemaClassId.KEY_CLASS + # SchemaClassId.KEY_TYPE + # SchemaClassId.KEY_HASH + # SchemaClass.KEY_SCHEMA_ID + # name of property (exist test only) + # name of method (exist test only) + + TARGET_AGENT="agent" + # allowed id: value: + # string name of agent + # allowed predicate keys(s): + # + KEY_AGENT_NAME="_name" + + TARGET_OBJECT_ID="object_id" + TARGET_OBJECT="object" + # If object is described by a schema, the value of the target map must + # include a "_schema_id": {map encoded schema id} value. + # + # allowed id: value: + # object_id string + # + # allowed predicate keys(s): + # + # QmfData.KEY_OBJECT_ID + # QmfData.KEY_UPDATE_TS + # QmfData.KEY_CREATE_TS + # QmfData.KEY_DELETE_TS + # <name of data value> + + # supported predicate operators + + # evaluation operators + QUOTE="quote" + UNQUOTE="unquote" + # boolean operators + EQ="eq" + NE="ne" + LT="lt" + LE="le" + GT="gt" + GE="ge" + RE_MATCH="re_match" + EXISTS="exists" + TRUE="true" + FALSE="false" + # logic operators + AND="and" + OR="or" + NOT="not" + + _valid_targets = [TARGET_PACKAGES, TARGET_OBJECT_ID, TARGET_SCHEMA, TARGET_SCHEMA_ID, + TARGET_OBJECT, TARGET_AGENT] + _valid_bool_ops = [EQ, NE, LT, GT, LE, GE, EXISTS, RE_MATCH, TRUE, FALSE] + _valid_logic_ops = [AND, OR, NOT] + _valid_eval_ops = [QUOTE, UNQUOTE] + + def __init__(self, _target=None, _target_params=None, _predicate=None, + _id=None, _map=None): + """ + """ + if _map is not None: + target_map = _map.get(self.KEY_TARGET) + if not target_map: + raise TypeError("QmfQuery requires a target map") + + _target = None + for key in target_map.iterkeys(): + if key in self._valid_targets: + _target = key + break + if _target is None: + raise TypeError("Invalid QmfQuery target: '%s'" % + str(target_map)) + + # convert target params from map format + _target_params = target_map.get(_target) + if _target_params: + if not isinstance(_target_params, type({})): + raise TypeError("target params must be a map: '%s'" % + str(_target_params)) + t_params = {} + for name,value in _target_params.iteritems(): + if name == QmfData.KEY_SCHEMA_ID: + t_params[name] = SchemaClassId.from_map(value) + else: + t_params[name] = value + _target_params = t_params + + _id = _map.get(self.KEY_ID) + if _id is not None: + # Convert identifier to native type if necessary + if _target == self.TARGET_SCHEMA: + _id = SchemaClassId.from_map(_id) + else: + _predicate = _map.get(self.KEY_PREDICATE, _predicate) + + self._target = _target + if not self._target: + raise TypeError("QmfQuery requires a target value") + self._target_params = _target_params + self._predicate = _predicate + self._id = _id + + # constructors + def _create_wildcard(cls, target, _target_params=None): + return cls(_target=target, _target_params=_target_params) + create_wildcard = classmethod(_create_wildcard) + + def _create_wildcard_object_id(cls, schema_id): + """ + Create a wildcard to match all object_ids for a given schema. + """ + if not isinstance(schema_id, SchemaClassId): + raise TypeError("class SchemaClassId expected") + params = {QmfData.KEY_SCHEMA_ID: schema_id} + return cls(_target=QmfQuery.TARGET_OBJECT_ID, + _target_params=params) + create_wildcard_object_id = classmethod(_create_wildcard_object_id) + + def _create_wildcard_object(cls, schema_id): + """ + Create a wildcard to match all objects for a given schema. + """ + if not isinstance(schema_id, SchemaClassId): + raise TypeError("class SchemaClassId expected") + params = {QmfData.KEY_SCHEMA_ID: schema_id} + return cls(_target=QmfQuery.TARGET_OBJECT, + _target_params=params) + create_wildcard_object = classmethod(_create_wildcard_object) + + def _create_predicate(cls, target, predicate, _target_params=None): + return cls(_target=target, _target_params=_target_params, + _predicate=predicate) + create_predicate = classmethod(_create_predicate) + + def _create_id(cls, target, ident, _target_params=None): + return cls(_target=target, _target_params=_target_params, _id=ident) + create_id = classmethod(_create_id) + + def _create_id_object(cls, object_id, _schema_id=None): + """ + Create a ID Query for an object (schema optional). + """ + if _schema_id is not None: + if not isinstance(_schema_id, SchemaClassId): + raise TypeError("class SchemaClassId expected") + params = {QmfData.KEY_SCHEMA_ID: _schema_id} + else: + params = None + return cls(_target=QmfQuery.TARGET_OBJECT, + _id=object_id, + _target_params=params) + create_id_object = classmethod(_create_id_object) + + def _create_id_object_id(cls, object_id, _schema_id=None): + """ + Create a ID Query for object_ids (schema optional). + """ + if _schema_id is not None: + if not isinstance(_schema_id, SchemaClassId): + raise TypeError("class SchemaClassId expected") + params = {QmfData.KEY_SCHEMA_ID: _schema_id} + else: + params = None + return cls(_target=QmfQuery.TARGET_OBJECT_ID, + _id=object_id, + _target_params=params) + create_id_object_id = classmethod(_create_id_object_id) + + def _from_map(cls, map_): + return cls(_map=map_) + from_map = classmethod(_from_map) + # end constructors + + def get_target(self): + return self._target + + def get_target_param(self): + return self._target_params + + def get_selector(self): + if self._id: + return QmfQuery.ID + else: + return QmfQuery.PREDICATE + + def get_id(self): + return self._id + + def get_predicate(self): + """ + """ + return self._predicate + + def evaluate(self, qmfData): + """ + """ + if self._id: + if self._target == self.TARGET_SCHEMA: + return (qmfData.has_value(qmfData.KEY_SCHEMA_ID) and + qmfData.get_value(qmfData.KEY_SCHEMA_ID) == self._id) + elif self._target == self.TARGET_OBJECT: + return (qmfData.has_value(qmfData.KEY_OBJECT_ID) and + qmfData.get_value(qmfData.KEY_OBJECT_ID) == self._id) + elif self._target == self.TARGET_AGENT: + return (qmfData.has_value(self.KEY_AGENT_NAME) and + qmfData.get_value(self.KEY_AGENT_NAME) == self._id) + + raise Exception("Unsupported query target '%s'" % str(self._target)) + + if self._predicate: + return self._eval_pred(self._predicate, qmfData) + # no predicate and no id - always match + return True + + def map_encode(self): + t_params = {} + if self._target_params: + for name,value in self._target_params.iteritems(): + if isinstance(value, _mapEncoder): + t_params[name] = value.map_encode() + else: + t_params[name] = value + if t_params: + _map = {self.KEY_TARGET: {self._target: t_params}} + else: + _map = {self.KEY_TARGET: {self._target: None}} + + if self._id is not None: + if isinstance(self._id, _mapEncoder): + _map[self.KEY_ID] = self._id.map_encode() + else: + _map[self.KEY_ID] = self._id + elif self._predicate is not None: + _map[self.KEY_PREDICATE] = self._predicate + return _map + + def _eval_pred(self, pred, qmfData): + """ + Evaluate the predicate expression against a QmfData object. + """ + if not isinstance(qmfData, QmfData): + raise TypeError("Query expects to evaluate QmfData types.") + + if not isinstance(pred, type([])): + log_query.warning("Invalid type for predicate expression: '%s'" % str(pred)) + return False + + # empty predicate - match all??? + if len(pred) == 0: + return True + + oper = pred[0] + if oper == QmfQuery.TRUE: + log_query.debug("query evaluate TRUE") + return True + + if oper == QmfQuery.FALSE: + log_query.debug("query evaluate FALSE") + return False + + if oper == QmfQuery.AND: + log_query.debug("query evaluate AND: '%s'" % str(pred)) + for exp in pred[1:]: + if not self._eval_pred(exp, qmfData): + log_query.debug("---> False") + return False + log_query.debug("---> True") + return True + + if oper == QmfQuery.OR: + log_query.debug("query evaluate OR: [%s]" % str(pred)) + for exp in pred[1:]: + if self._eval_pred(exp, qmfData): + log_query.debug("---> True") + return True + log_query.debug("---> False") + return False + + if oper == QmfQuery.NOT: + log_query.debug("query evaluate NOT: [%s]" % str(pred)) + for exp in pred[1:]: + if self._eval_pred(exp, qmfData): + log_query.debug("---> False") + return False + log_query.debug("---> True") + return True + + if oper == QmfQuery.EXISTS: + if len(pred) != 2: + log_query.warning("Malformed query: 'exists' operator" + " - bad arguments '%s'" % str(pred)) + return False + ### Q: Should we assume "quote", or should it be explicit? + ### "foo" or ["quote" "foo"] + ### my guess is "explicit" + log_query.debug("query evaluate EXISTS: [%s]" % str(pred)) + try: + arg = self._fetch_pred_arg(pred[1], qmfData) + except AttributeError: + log_query.debug("query parameter not found: '%s'" % str(pred)) + return False + v = qmfData.has_value(arg) + log_query.debug("---> %s" % str(v)) + return v + + # binary operators + if oper in [QmfQuery.EQ, QmfQuery.NE, QmfQuery.LT, + QmfQuery.LE, QmfQuery.GT, QmfQuery.GE, + QmfQuery.RE_MATCH]: + if len(pred) != 3: + log_query.warning("Malformed query: '%s' operator" + " - requires 2 arguments '%s'" % + (oper, str(pred))) + return False + # @todo: support regular expression match + log_query.debug("query evaluate binary op: [%s]" % str(pred)) + try: + arg1 = self._fetch_pred_arg(pred[1], qmfData) + arg2 = self._fetch_pred_arg(pred[2], qmfData) + except AttributeError: + log_query.debug("query parameter not found: '%s'" % str(pred)) + return False + log_query.debug("query evaluate %s: %s, %s" % (oper, str(arg1), str(arg2))) + v = False + try: + if oper == QmfQuery.EQ: v = arg1 == arg2 + elif oper == QmfQuery.NE: v = arg1 != arg2 + elif oper == QmfQuery.LT: v = arg1 < arg2 + elif oper == QmfQuery.LE: v = arg1 <= arg2 + elif oper == QmfQuery.GT: v = arg1 > arg2 + elif oper == QmfQuery.GE: v = arg1 >= arg2 + except TypeError: + log_query.warning("query comparison failed: '%s'" % str(pred)) + log_query.debug("---> %s" % str(v)) + return v + + log_query.warning("Unrecognized query operator: [%s]" % str(pred[0])) + return False + + def _fetch_pred_arg(self, arg, qmfData): + """ + Determine the value of a predicate argument by evaluating quoted + arguments. + """ + if isinstance(arg, basestring): + return qmfData.get_value(arg) + if isinstance(arg, type([])) and len(arg) == 2: + if arg[0] == QmfQuery.QUOTE: + return arg[1] + if arg[0] == QmfQuery.UNQUOTE: + return qmfData.get_value(arg[1]) + return arg + + def __repr__(self): + return "QmfQuery=<<" + str(self.map_encode()) + ">>" + + + + + +##============================================================================== +## SCHEMA +##============================================================================== + + +# Argument typecodes, access, and direction qualifiers + +class qmfTypes(object): + TYPE_UINT8 = 1 + TYPE_UINT16 = 2 + TYPE_UINT32 = 3 + TYPE_UINT64 = 4 + + TYPE_SSTR = 6 + TYPE_LSTR = 7 + + TYPE_ABSTIME = 8 + TYPE_DELTATIME = 9 + + TYPE_REF = 10 + + TYPE_BOOL = 11 + + TYPE_FLOAT = 12 + TYPE_DOUBLE = 13 + + TYPE_UUID = 14 + + TYPE_MAP = 15 + + TYPE_INT8 = 16 + TYPE_INT16 = 17 + TYPE_INT32 = 18 + TYPE_INT64 = 19 + + TYPE_OBJECT = 20 + + TYPE_LIST = 21 + + TYPE_ARRAY = 22 + +# New subtypes: +# integer (for time, duration, signed/unsigned) +# double (float) +# bool +# string +# map (ref, qmfdata) +# list +# uuid + + +class qmfAccess(object): + READ_CREATE = 1 + READ_WRITE = 2 + READ_ONLY = 3 + + +class qmfDirection(object): + DIR_IN = 1 + DIR_OUT = 2 + DIR_IN_OUT = 3 + + + +def _to_bool( param ): + """ + Helper routine to convert human-readable representations of + boolean values to python bool types. + """ + _false_strings = ["off", "no", "false", "0", "none"] + _true_strings = ["on", "yes", "true", "1"] + if type(param) == str: + lparam = param.lower() + if lparam in _false_strings: + return False + if lparam in _true_strings: + return True + raise TypeError("unrecognized boolean string: '%s'" % param ) + else: + return bool(param) + + + +class SchemaClassId(_mapEncoder): + """ + Unique identifier for an instance of a SchemaClass. + + Map format: + map["package_name"] = str, name of associated package + map["class_name"] = str, name of associated class + map["type"] = str, "data"|"event", default: "data" + optional: + map["hash_str"] = str, hash value in standard format or None + if hash is unknown. + """ + KEY_PACKAGE="_package_name" + KEY_CLASS="_class_name" + KEY_TYPE="_type" + KEY_HASH="_hash_str" + + TYPE_DATA = "_data" + TYPE_EVENT = "_event" + + _valid_types=[TYPE_DATA, TYPE_EVENT] + _schemaHashStrFormat = "%08x-%08x-%08x-%08x" + _schemaHashStrDefault = "00000000-00000000-00000000-00000000" + + def __init__(self, pname=None, cname=None, stype=TYPE_DATA, hstr=None, + _map=None): + """ + @type pname: str + @param pname: the name of the class's package + @type cname: str + @param cname: name of the class + @type stype: str + @param stype: schema type [data | event] + @type hstr: str + @param hstr: the hash value in '%08x-%08x-%08x-%08x' format + """ + if _map is not None: + # construct from map + pname = _map.get(self.KEY_PACKAGE, pname) + cname = _map.get(self.KEY_CLASS, cname) + stype = _map.get(self.KEY_TYPE, stype) + hstr = _map.get(self.KEY_HASH, hstr) + + self._pname = pname + self._cname = cname + if stype not in SchemaClassId._valid_types: + raise TypeError("Invalid SchemaClassId type: '%s'" % stype) + self._type = stype + self._hstr = hstr + if self._hstr: + try: + # sanity check the format of the hash string + hexValues = hstr.split("-") + h0 = int(hexValues[0], 16) + h1 = int(hexValues[1], 16) + h2 = int(hexValues[2], 16) + h3 = int(hexValues[3], 16) + except: + raise Exception("Invalid SchemaClassId format: bad hash string: '%s':" + % hstr) + # constructor + def _create(cls, pname, cname, stype=TYPE_DATA, hstr=None): + return cls(pname=pname, cname=cname, stype=stype, hstr=hstr) + create = classmethod(_create) + + # map constructor + def _from_map(cls, map_): + return cls(_map=map_) + from_map = classmethod(_from_map) + + def get_package_name(self): + """ + Access the package name in the SchemaClassId. + + @rtype: str + """ + return self._pname + + + def get_class_name(self): + """ + Access the class name in the SchemaClassId + + @rtype: str + """ + return self._cname + + + def get_hash_string(self): + """ + Access the schema's hash as a string value + + @rtype: str + """ + return self._hstr + + + def get_type(self): + """ + Returns the type code associated with this Schema + + @rtype: str + """ + return self._type + + def map_encode(self): + _map = {} + _map[self.KEY_PACKAGE] = self._pname + _map[self.KEY_CLASS] = self._cname + _map[self.KEY_TYPE] = self._type + if self._hstr: _map[self.KEY_HASH] = self._hstr + return _map + + def __repr__(self): + hstr = self.get_hash_string() + if not hstr: + hstr = SchemaClassId._schemaHashStrDefault + return self._pname + ":" + self._cname + ":" + self._type + "(" + hstr + ")" + + + def __cmp__(self, other): + if isinstance(other, dict): + other = SchemaClassId.from_map(other) + if not isinstance(other, SchemaClassId): + raise TypeError("Invalid types for compare") + # return 1 + me = str(self) + them = str(other) + if me < them: + return -1 + if me > them: + return 1 + return 0 + + + def __hash__(self): + return (self._pname, self._cname, self._hstr).__hash__() + + + +class SchemaProperty(_mapEncoder): + """ + Describes the structure of a Property data object. + Map format: + map["amqp_type"] = int, AMQP type code indicating property's data type + + optional: + map["access"] = str, access allowed to this property, default "RO" + map["index"] = bool, True if this property is an index value, default False + map["optional"] = bool, True if this property is optional, default False + map["unit"] = str, describes units used + map["min"] = int, minimum allowed value + map["max"] = int, maximun allowed value + map["maxlen"] = int, if string type, this is the maximum length in bytes + required to represent the longest instance of this string. + map["desc"] = str, human-readable description of this argument + map["reference"] = str, ??? + map["parent_ref"] = bool, true if this property references an object in + which this object is in a child-parent relationship. Default False + """ + __hash__ = None + _access_strings = ["RO","RW","RC"] + _dir_strings = ["I", "O", "IO"] + def __init__(self, _type_code=None, _map=None, kwargs={}): + if _map is not None: + # construct from map + _type_code = _map.get("amqp_type", _type_code) + kwargs = _map + if not _type_code: + raise TypeError("SchemaProperty: amqp_type is a mandatory" + " parameter") + + self._type = _type_code + self._access = "RO" + self._isIndex = False + self._isOptional = False + self._unit = None + self._min = None + self._max = None + self._maxlen = None + self._desc = None + self._reference = None + self._isParentRef = False + self._dir = None + self._default = None + + for key, value in kwargs.items(): + if key == "access": + value = str(value).upper() + if value not in self._access_strings: + raise TypeError("invalid value for access parameter: '%s':" % value ) + self._access = value + elif key == "index" : self._isIndex = _to_bool(value) + elif key == "optional": self._isOptional = _to_bool(value) + elif key == "unit" : self._unit = value + elif key == "min" : self._min = value + elif key == "max" : self._max = value + elif key == "maxlen" : self._maxlen = value + elif key == "desc" : self._desc = value + elif key == "reference" : self._reference = value + elif key == "parent_ref" : self._isParentRef = _to_bool(value) + elif key == "dir": + value = str(value).upper() + if value not in self._dir_strings: + raise TypeError("invalid value for direction parameter: '%s'" % value) + self._dir = value + elif key == "default" : self._default = value + + # constructor + def _create(cls, type_code, kwargs={}): + return cls(_type_code=type_code, kwargs=kwargs) + create = classmethod(_create) + + # map constructor + def _from_map(cls, map_): + return cls(_map=map_) + from_map = classmethod(_from_map) + + def get_type(self): return self._type + + def get_access(self): return self._access + + def is_optional(self): return self._isOptional + + def is_index(self): return self._isIndex + + def get_unit(self): return self._unit + + def get_min(self): return self._min + + def get_max(self): return self._max + + def get_max_len(self): return self._maxlen + + def get_desc(self): return self._desc + + def get_reference(self): return self._reference + + def is_parent_ref(self): return self._isParentRef + + def get_direction(self): return self._dir + + def get_default(self): return self._default + + def map_encode(self): + """ + Return the map encoding of this schema. + """ + _map = {} + _map["amqp_type"] = self._type + _map["access"] = self._access + _map["index"] = self._isIndex + _map["optional"] = self._isOptional + if self._unit: _map["unit"] = self._unit + if self._min: _map["min"] = self._min + if self._max: _map["max"] = self._max + if self._maxlen: _map["maxlen"] = self._maxlen + if self._desc: _map["desc"] = self._desc + if self._reference: _map["reference"] = self._reference + _map["parent_ref"] = self._isParentRef + if self._dir: _map["dir"] = self._dir + if self._default: _map["default"] = self._default + return _map + + def __repr__(self): + return "SchemaProperty=<<" + str(self.map_encode()) + ">>" + + def _update_hash(self, hasher): + """ + Update the given hash object with a hash computed over this schema. + """ + hasher.update(str(self._type)) + hasher.update(str(self._isIndex)) + hasher.update(str(self._isOptional)) + if self._access: hasher.update(self._access) + if self._unit: hasher.update(self._unit) + if self._desc: hasher.update(self._desc) + if self._dir: hasher.update(self._dir) + if self._default: hasher.update(self._default) + + + +class SchemaMethod(_mapEncoder): + """ + The SchemaMethod class describes the method's structure, and contains a + SchemaProperty class for each argument declared by the method. + + Map format: + map["arguments"] = map of "name"=<SchemaProperty> pairs. + map["desc"] = str, description of the method + """ + KEY_NAME="_name" + KEY_ARGUMENTS="_arguments" + KEY_DESC="_desc" + KEY_ERROR="_error" + def __init__(self, _args={}, _desc=None, _map=None): + """ + Construct a SchemaMethod. + + @type args: map of "name"=<SchemaProperty> objects + @param args: describes the arguments accepted by the method + @type _desc: str + @param _desc: Human-readable description of the schema + """ + if _map is not None: + _desc = _map.get(self.KEY_DESC) + margs = _map.get(self.KEY_ARGUMENTS) + if margs: + # margs are in map format - covert to SchemaProperty + tmp_args = {} + for name,val in margs.iteritems(): + tmp_args[name] = SchemaProperty.from_map(val) + _args=tmp_args + + self._arguments = _args.copy() + self._desc = _desc + + # map constructor + def _from_map(cls, map_): + return cls(_map=map_) + from_map = classmethod(_from_map) + + def get_desc(self): return self._desc + + def get_arg_count(self): return len(self._arguments) + + def get_arguments(self): return self._arguments.copy() + + def get_argument(self, name): return self._arguments.get(name) + + def add_argument(self, name, schema): + """ + Add an argument to the list of arguments passed to this method. + Used by an agent for dynamically creating method schema. + + @type name: string + @param name: name of new argument + @type schema: SchemaProperty + @param schema: SchemaProperty to add to this method + """ + if not isinstance(schema, SchemaProperty): + raise TypeError("argument must be a SchemaProperty class") + # "Input" argument, by default + if schema._dir is None: + schema._dir = "I" + self._arguments[name] = schema + + def map_encode(self): + """ + Return the map encoding of this schema. + """ + _map = {} + _args = {} + for name,val in self._arguments.iteritems(): + _args[name] = val.map_encode() + _map[self.KEY_ARGUMENTS] = _args + if self._desc: _map[self.KEY_DESC] = self._desc + return _map + + def __repr__(self): + result = "SchemaMethod=<<args=(" + first = True + for name,arg in self._arguments.iteritems(): + if first: + first = False + else: + result += ", " + result += name + result += ")>>" + return result + + def _update_hash(self, hasher): + """ + Update the given hash object with a hash computed over this schema. + """ + for name,val in self._arguments.iteritems(): + hasher.update(name) + val._update_hash(hasher) + if self._desc: hasher.update(self._desc) + + + +class SchemaClass(QmfData): + """ + Base class for Data and Event Schema classes. + + Map format: + map(QmfData), plus: + map["_schema_id"] = map representation of a SchemaClassId instance + map["_primary_key_names"] = order list of primary key names + """ + KEY_PRIMARY_KEY_NAMES="_primary_key_names" + KEY_DESC = "_desc" + + SUBTYPE_PROPERTY="qmfProperty" + SUBTYPE_METHOD="qmfMethod" + + def __init__(self, _classId=None, _desc=None, _map=None): + """ + Schema Class constructor. + + @type classId: class SchemaClassId + @param classId: Identifier for this SchemaClass + @type _desc: str + @param _desc: Human-readable description of the schema + """ + if _map is not None: + super(SchemaClass, self).__init__(_map=_map) + + # decode each value based on its type + for name,value in self._values.iteritems(): + if self._subtypes.get(name) == self.SUBTYPE_METHOD: + self._values[name] = SchemaMethod.from_map(value) + else: + self._values[name] = SchemaProperty.from_map(value) + cid = _map.get(self.KEY_SCHEMA_ID) + if cid: + _classId = SchemaClassId.from_map(cid) + self._object_id_names = _map.get(self.KEY_PRIMARY_KEY_NAMES,[]) + _desc = _map.get(self.KEY_DESC) + else: + if _classId is None: + raise Exception("A class identifier must be supplied.") + super(SchemaClass, self).__init__(_object_id=str(_classId)) + self._object_id_names = [] + + self._classId = _classId + self._desc = _desc + + def get_class_id(self): + if not self._classId.get_hash_string(): + self.generate_hash() + return self._classId + + def get_desc(self): return self._desc + + def generate_hash(self): + """ + generate an md5 hash over the body of the schema, + and return a string representation of the hash + in format "%08x-%08x-%08x-%08x" + """ + md5Hash = _md5Obj() + md5Hash.update(self._classId.get_package_name()) + md5Hash.update(self._classId.get_class_name()) + md5Hash.update(self._classId.get_type()) + for name,x in self._values.iteritems(): + md5Hash.update(name) + x._update_hash( md5Hash ) + for name,value in self._subtypes.iteritems(): + md5Hash.update(name) + md5Hash.update(value) + idx = 0 + for name in self._object_id_names: + md5Hash.update(str(idx) + name) + idx += 1 + hstr = md5Hash.hexdigest()[0:8] + "-" +\ + md5Hash.hexdigest()[8:16] + "-" +\ + md5Hash.hexdigest()[16:24] + "-" +\ + md5Hash.hexdigest()[24:32] + # update classId with new hash value + self._classId._hstr = hstr + return hstr + + + def get_property_count(self): + count = 0 + for value in self._subtypes.itervalues(): + if value == self.SUBTYPE_PROPERTY: + count += 1 + return count + + def get_properties(self): + props = {} + for name,value in self._subtypes.iteritems(): + if value == self.SUBTYPE_PROPERTY: + props[name] = self._values.get(name) + return props + + def get_property(self, name): + if self._subtypes.get(name) == self.SUBTYPE_PROPERTY: + return self._values.get(name) + return None + + def add_property(self, name, prop): + self.set_value(name, prop, self.SUBTYPE_PROPERTY) + # need to re-generate schema hash + self._classId._hstr = None + + def get_value(self, name): + # check for meta-properties first + if name == SchemaClassId.KEY_PACKAGE: + return self._classId.get_package_name() + if name == SchemaClassId.KEY_CLASS: + return self._classId.get_class_name() + if name == SchemaClassId.KEY_TYPE: + return self._classId.get_type() + if name == SchemaClassId.KEY_HASH: + return self.get_class_id().get_hash_string() + if name == self.KEY_SCHEMA_ID: + return self.get_class_id() + if name == self.KEY_PRIMARY_KEY_NAMES: + return self._object_id_names[:] + return super(SchemaClass, self).get_value(name) + + def has_value(self, name): + if name in [SchemaClassId.KEY_PACKAGE, SchemaClassId.KEY_CLASS, SchemaClassId.KEY_TYPE, + SchemaClassId.KEY_HASH, self.KEY_SCHEMA_ID, self.KEY_PRIMARY_KEY_NAMES]: + return True + super(SchemaClass, self).has_value(name) + + def map_encode(self): + """ + Return the map encoding of this schema. + """ + _map = super(SchemaClass,self).map_encode() + _map[self.KEY_SCHEMA_ID] = self.get_class_id().map_encode() + if self._object_id_names: + _map[self.KEY_PRIMARY_KEY_NAMES] = self._object_id_names[:] + if self._desc: + _map[self.KEY_DESC] = self._desc + return _map + + def __repr__(self): + return str(self.get_class_id()) + + + +class SchemaObjectClass(SchemaClass): + """ + A schema class that describes a data object. The data object is composed + of zero or more properties and methods. An instance of the SchemaObjectClass + can be identified using a key generated by concantenating the values of + all properties named in the primary key list. + + Map format: + map(SchemaClass) + """ + def __init__(self, _classId=None, _desc=None, + _props={}, _methods={}, _object_id_names=[], + _map=None): + """ + @type pname: str + @param pname: name of package this schema belongs to + @type cname: str + @param cname: class name for this schema + @type desc: str + @param desc: Human-readable description of the schema + @type _hash: str + @param _methods: hash computed on the body of this schema, if known + @type _props: map of 'name':<SchemaProperty> objects + @param _props: all properties provided by this schema + @type _pkey: list of strings + @param _pkey: names of each property to be used for constructing the primary key + @type _methods: map of 'name':<SchemaMethod> objects + @param _methods: all methods provided by this schema + """ + if _map is not None: + super(SchemaObjectClass,self).__init__(_map=_map) + else: + super(SchemaObjectClass, self).__init__(_classId=_classId, _desc=_desc) + self._object_id_names = _object_id_names + for name,value in _props.iteritems(): + self.set_value(name, value, self.SUBTYPE_PROPERTY) + for name,value in _methods.iteritems(): + self.set_value(name, value, self.SUBTYPE_METHOD) + + if self._classId.get_type() != SchemaClassId.TYPE_DATA: + raise TypeError("Invalid ClassId type for data schema: %s" % self._classId) + + # map constructor + def __from_map(cls, map_): + return cls(_map=map_) + from_map = classmethod(__from_map) + + def get_id_names(self): + return self._object_id_names[:] + + def get_method_count(self): + count = 0 + for value in self._subtypes.itervalues(): + if value == self.SUBTYPE_METHOD: + count += 1 + return count + + def get_methods(self): + meths = {} + for name,value in self._subtypes.iteritems(): + if value == self.SUBTYPE_METHOD: + meths[name] = self._values.get(name) + return meths + + def get_method(self, name): + if self._subtypes.get(name) == self.SUBTYPE_METHOD: + return self._values.get(name) + return None + + def add_method(self, name, method): + self.set_value(name, method, self.SUBTYPE_METHOD) + # need to re-generate schema hash + self._classId._hstr = None + + + + +class SchemaEventClass(SchemaClass): + """ + A schema class that describes an event. The event is composed + of zero or more properties. + + Map format: + map["schema_id"] = map, SchemaClassId map for this object. + map["desc"] = string description of this schema + map["properties"] = map of "name":SchemaProperty values. + """ + def __init__(self, _classId=None, _desc=None, _props={}, + _map=None): + if _map is not None: + super(SchemaEventClass,self).__init__(_map=_map) + else: + super(SchemaEventClass, self).__init__(_classId=_classId, + _desc=_desc) + for name,value in _props.iteritems(): + self.set_value(name, value, self.SUBTYPE_PROPERTY) + + if self._classId.get_type() != SchemaClassId.TYPE_EVENT: + raise TypeError("Invalid ClassId type for event schema: %s" % + self._classId) + + # map constructor + def __from_map(cls, map_): + return cls(_map=map_) + from_map = classmethod(__from_map) + |