diff options
Diffstat (limited to 'qpid/python/qmf2/common.py')
-rw-r--r-- | qpid/python/qmf2/common.py | 1881 |
1 files changed, 1881 insertions, 0 deletions
diff --git a/qpid/python/qmf2/common.py b/qpid/python/qmf2/common.py new file mode 100644 index 0000000000..061c9fbe78 --- /dev/null +++ b/qpid/python/qmf2/common.py @@ -0,0 +1,1881 @@ + +# 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 +import logging +from threading import Lock +from threading import Condition +try: + import hashlib + _md5Obj = hashlib.md5 +except ImportError: + import md5 + _md5Obj = md5.new + + + + +## +## Constants +## + + +AMQP_QMF_AGENT_LOCATE = "agent.locate" +AMQP_QMF_AGENT_INDICATION = "agent.ind" +AMQP_QMF_AGENT_EVENT="agent.event" +# agent.ind[.<agent-name>] +# agent.event.<sev>.<agent-name> +# sev="strings" +# + +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 makeSubject(_code): + """ + Create a message subject field value. + """ + return AMQP_QMF_SUBJECT_FMT % (AMQP_QMF_SUBJECT, AMQP_QMF_VERSION, _code) + + +def parseSubject(_sub): + """ + Deconstruct a subject field, return version,opcode values + """ + if _sub[:3] != "qmf": + raise Exception("Non-QMF message received") + + return _sub[3:].split('.', 1) + + +##============================================================================== +## 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 + # 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): + """ + TBD + """ + TYPE_DIRECT = "direct" + TYPE_TOPIC = "topic" + + ADDRESS_FMT = "qmf.%s.%s/%s" + DEFAULT_DOMAIN = "default" + + + def __init__(self, name, domain, type_): + self._name = name + self._domain = domain + self._type = type_ + + def _direct(cls, name, _domain=None): + if _domain is None: + _domain = QmfAddress.DEFAULT_DOMAIN + return cls(name, _domain, type_=QmfAddress.TYPE_DIRECT) + direct = classmethod(_direct) + + def _topic(cls, name, _domain=None): + if _domain is None: + _domain = QmfAddress.DEFAULT_DOMAIN + return cls(name, _domain, type_=QmfAddress.TYPE_TOPIC) + topic = classmethod(_topic) + + + def get_address(self): + return str(self) + + def __repr__(self): + return QmfAddress.ADDRESS_FMT % (self._domain, self._type, self._name) + + + + +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 data class representing arbitrarily structure data. No schema or + managing agent is associated with data of this class. + + 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, + _ctime = 0, _utime = 0, _dtime = 0, + _map=None, + _schema=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 + """ + self._schema_id = None + 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: + self._schema_id = SchemaClassId(_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)) + + self._values = _values.copy() + self._subtypes = _subtypes.copy() + self._tag = _tag + self._ctime = _ctime + self._utime = _utime + self._dtime = _dtime + self._const = _const + + if _object_id is not None: + self._object_id = str(_object_id) + else: + self._object_id = None + + if _schema is not None: + self._set_schema(_schema) + else: + # careful: map constructor may have already set self._schema_id, do + # not override it! + self._schema = None + + def _create(cls, values, _subtypes={}, _tag=None, _object_id=None, + _schema=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=_schema, _const=_const) + create = classmethod(_create) + + def __from_map(cls, map_, _schema=None, _const=False): + return cls(_map=map_, _schema=_schema, _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): + # meta-properties: + 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 + + return self._values.get(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. + """ + if self._object_id: + return self._object_id + + # if object id not assigned, see if schema defines a set of field + # values to use as an id + if not self._schema: + return None + + ids = self._schema.get_id_names() + if not ids: + return None + + if not self._validated: + self._validate() + + result = u"" + for key in ids: + try: + result += unicode(self._values[key]) + except: + logging.error("get_object_id(): cannot convert value '%s'." + % key) + return None + self._object_id = result + return result + + 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 _set_schema(self, schema): + self._validated = False + self._schema = schema + if schema: + self._schema_id = schema.get_class_id() + if self._const: + self._validate() + else: + self._schema_id = None + + def _validate(self): + """ + Compares this object's data against the associated schema. Throws an + exception if the data does not conform to the schema. + """ + props = self._schema.get_properties() + for name,val in props.iteritems(): + # @todo validate: type compatible with amqp_type? + # @todo validate: primary keys have values + if name not in self._values: + if val._isOptional: + # ok not to be present, put in dummy value + # to simplify access + self._values[name] = None + else: + raise Exception("Required property '%s' not present." % name) + self._validated = True + + 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=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, _schema=_schema, + _const=_const) + _timestamp = _map.get(self.KEY_TIMESTAMP, _timestamp) + _sev = _map.get(self.KEY_SEVERITY, _sev) + else: + super(QmfEvent, self).__init__(_values=_values, + _subtypes=_subtypes, _tag=_tag, + _schema=_schema, _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=None, _const=False): + return cls(_timestamp=timestamp, _sev=severity, _values=values, + _subtypes=_subtypes, _tag=_tag, _schema=_schema, _const=_const) + create = classmethod(_create) + + def _from_map(cls, map_, _schema=None, _const=False): + return cls(_map=map_, _schema=_schema, _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 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 predicate keys(s): + # + KEY_AGENT_NAME="_name" + + TARGET_OBJECT_ID="object_id" + TARGET_OBJECT="object" + # allowed predicate keys(s): + # + # SchemaClassId.KEY_PACKAGE + # SchemaClassId.KEY_CLASS + # SchemaClassId.KEY_TYPE + # SchemaClassId.KEY_HASH + # QmfData.KEY_SCHEMA_ID + # QmfData.KEY_OBJECT_ID + # QmfData.KEY_UPDATE_TS + # QmfData.KEY_CREATE_TS + # QmfData.KEY_DELETE_TS + # <name of data value> + + CMP_EQ="eq" + CMP_NE="ne" + CMP_LT="lt" + CMP_LE="le" + CMP_GT="gt" + CMP_GE="ge" + CMP_RE_MATCH="re_match" + CMP_EXISTS="exists" + CMP_TRUE="true" + CMP_FALSE="false" + + LOGIC_AND="and" + LOGIC_OR="or" + LOGIC_NOT="not" + + _valid_targets = [TARGET_PACKAGES, TARGET_OBJECT_ID, TARGET_SCHEMA, TARGET_SCHEMA_ID, + TARGET_OBJECT, TARGET_AGENT] + + 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 + + _target_params = target_map.get(_target) + + _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: + pred = _map.get(self.KEY_PREDICATE) + if pred: + _predicate = QmfQueryPredicate(pred) + + 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_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 _from_map(cls, map_): + return cls(_map=map_) + from_map = classmethod(_from_map) + + 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._predicate.evaluate(qmfData) + # no predicate and no id - always match + return True + + def map_encode(self): + _map = {self.KEY_TARGET: {self._target: self._target_params}} + 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.map_encode() + return _map + + def __repr__(self): + return "QmfQuery=<<" + str(self.map_encode()) + ">>" + + + +class QmfQueryPredicate(_mapEncoder): + """ + Class for Query predicates. + """ + _valid_cmp_ops = [QmfQuery.CMP_EQ, QmfQuery.CMP_NE, QmfQuery.CMP_LT, + QmfQuery.CMP_GT, QmfQuery.CMP_LE, QmfQuery.CMP_GE, + QmfQuery.CMP_EXISTS, QmfQuery.CMP_RE_MATCH, + QmfQuery.CMP_TRUE, QmfQuery.CMP_FALSE] + _valid_logic_ops = [QmfQuery.LOGIC_AND, QmfQuery.LOGIC_OR, QmfQuery.LOGIC_NOT] + + + def __init__( self, pmap): + """ + {"op": listOf(operands)} + """ + self._oper = None + self._operands = [] + + logic_op = False + if type(pmap) == dict: + for key in pmap.iterkeys(): + if key in self._valid_cmp_ops: + # comparison operation - may have "name" and "value" + self._oper = key + break + if key in self._valid_logic_ops: + logic_op = True + self._oper = key + break + + if not self._oper: + raise TypeError("invalid predicate expression: '%s'" % str(pmap)) + + if type(pmap[self._oper]) == list or type(pmap[self._oper]) == tuple: + if logic_op: + for exp in pmap[self._oper]: + self.append(QmfQueryPredicate(exp)) + else: + self._operands = list(pmap[self._oper]) + + else: + raise TypeError("invalid predicate: '%s'" % str(pmap)) + + + def append(self, operand): + """ + Append another operand to a predicate expression + """ + self._operands.append(operand) + + + + def evaluate( self, qmfData ): + """ + """ + if not isinstance(qmfData, QmfData): + raise TypeError("Query expects to evaluate QmfData types.") + + if self._oper == QmfQuery.CMP_TRUE: + logging.debug("query evaluate TRUE") + return True + if self._oper == QmfQuery.CMP_FALSE: + logging.debug("query evaluate FALSE") + return False + + if self._oper in [QmfQuery.CMP_EQ, QmfQuery.CMP_NE, QmfQuery.CMP_LT, + QmfQuery.CMP_LE, QmfQuery.CMP_GT, QmfQuery.CMP_GE, + QmfQuery.CMP_RE_MATCH]: + if len(self._operands) != 2: + logging.warning("Malformed query compare expression received: '%s, %s'" % + (self._oper, str(self._operands))) + return False + # @todo: support regular expression match + name = self._operands[0] + logging.debug("looking for: '%s'" % str(name)) + if not qmfData.has_value(name): + logging.warning("Malformed query, attribute '%s' not present." + % name) + return False + + arg1 = qmfData.get_value(name) + arg2 = self._operands[1] + logging.debug("query evaluate %s: '%s' '%s' '%s'" % + (name, str(arg1), self._oper, str(arg2))) + try: + if self._oper == QmfQuery.CMP_EQ: return arg1 == arg2 + if self._oper == QmfQuery.CMP_NE: return arg1 != arg2 + if self._oper == QmfQuery.CMP_LT: return arg1 < arg2 + if self._oper == QmfQuery.CMP_LE: return arg1 <= arg2 + if self._oper == QmfQuery.CMP_GT: return arg1 > arg2 + if self._oper == QmfQuery.CMP_GE: return arg1 >= arg2 + if self._oper == QmfQuery.CMP_RE_MATCH: + logging.error("!!! RE QUERY TBD !!!") + return False + except: + pass + logging.warning("Malformed query - %s: '%s' '%s' '%s'" % + (name, str(arg1), self._oper, str(self._operands[1]))) + return False + + + if self._oper == QmfQuery.CMP_EXISTS: + if len(self._operands) != 1: + logging.warning("Malformed query present expression received") + return False + name = self._operands[0] + logging.debug("query evaluate PRESENT: [%s]" % str(name)) + return qmfData.has_value(name) + + if self._oper == QmfQuery.LOGIC_AND: + logging.debug("query evaluate AND: '%s'" % str(self._operands)) + for exp in self._operands: + if not exp.evaluate(qmfData): + return False + return True + + if self._oper == QmfQuery.LOGIC_OR: + logging.debug("query evaluate OR: [%s]" % str(self._operands)) + for exp in self._operands: + if exp.evaluate(qmfData): + return True + return False + + if self._oper == QmfQuery.LOGIC_NOT: + logging.debug("query evaluate NOT: [%s]" % str(self._operands)) + for exp in self._operands: + if exp.evaluate(qmfData): + return False + return True + + logging.warning("Unrecognized query operator: [%s]" % str(self._oper)) + return False + + + def map_encode(self): + _map = {} + _list = [] + for exp in self._operands: + if isinstance(exp, QmfQueryPredicate): + _list.append(exp.map_encode()) + else: + _list.append(exp) + _map[self._oper] = _list + return _map + + + def __repr__(self): + return "QmfQueryPredicate=<<" + 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 _toBool( 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 = _toBool(value) + elif key == "optional": self._isOptional = _toBool(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 = _toBool(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 getType(self): return self._type + + def getAccess(self): return self._access + + def is_optional(self): return self._isOptional + + def isIndex(self): return self._isIndex + + def getUnit(self): return self._unit + + def getMin(self): return self._min + + def getMax(self): return self._max + + def getMaxLen(self): return self._maxlen + + def getDesc(self): return self._desc + + def getReference(self): return self._reference + + def isParentRef(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 _updateHash(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 _updateHash(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._updateHash(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: + super(SchemaClass, self).__init__() + 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._updateHash( 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=None, + _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) + + + + + + + + + + + |