def _get_attribute_metadata(self, path):
        if not path.endswith(":meta"):
            path = quote(unquote(path) + ":meta")

        _content_instance, data = list(self.get_content_instances(path))[0]

        return (data["type"], data["isDomain"],
                self._parse_metadata(data["metadata"]))
Exemplo n.º 2
0
    def _create_subscriptions_db_container(self):
        containers_path = "/applications/" + quote(
            self.internal_app_id) + "/containers"

        try:
            self.scl.create(containers_path,
                            Container(id=self.prefix + "subscription_db"))
        except SCLNotFound:
            self.scl.create("/applications",
                            Application(appId=self.internal_app_id))
            self._create_subscriptions_db_container()
Exemplo n.º 3
0
 def _content_instances_path(self):
     return ("/applications/" + quote(self.internal_app_id) +
             "/containers/" + self.prefix +
             "subscription_db/contentInstances")
    def discoverContextAvailability(self, discoverContextAvailabilityRequest):
        relevant_apps = self._get_relevant_apps(
            discoverContextAvailabilityRequest)

        result_apps = set()
        attributes = []
        cache = {}

        for entity_type, entity_id, path in relevant_apps:
            containers_path = path + "/containers"

            try:
                containers = cache[containers_path]
            except KeyError:
                containers = cache[containers_path] = self.scl.retrieve(
                    containers_path).resource

            for reference in containers.containerCollection:
                if (not unquote(reference.id).endswith(":meta") and
                    (not discoverContextAvailabilityRequest.attributeList
                     or reference.id
                     in discoverContextAvailabilityRequest.attributeList)):
                    result_apps.add((entity_type, entity_id))

                    try:
                        metadata = cache[reference.id]
                    except KeyError:
                        try:
                            #ci_path = unquote(reference["$t"]) + ":meta/contentInstances"
                            ci_path = unquote(
                                containers_path + '/' +
                                reference.id) + ":meta/contentInstances"
                            meta_content_instances = self.scl.retrieve(
                                quote(ci_path)).resource
                            b64json = meta_content_instances.contentInstanceCollection[
                                0].content["$t"]
                            metadata = cache[reference.id] = loads(
                                b64decode(b64json))
                        except SCLNotFound:
                            metadata = cache[reference.id] = {
                                "type": "Generic",
                                "isDomain": False,
                                "metadata": []
                            }

                        attributes.append((reference.id, metadata))

        if not result_apps:
            return DiscoverContextAvailabilityResponse(errorCode=404)

        entity_ids = [
            EntityId(type=entity_type, id=entity_id, isPattern=False)
            for entity_type, entity_id in result_apps
        ]

        context_attributes = []

        for name, metadata in attributes:
            context_attributes.append(
                ContextRegistrationAttribute(name=name,
                                             type=metadata["type"],
                                             isDomain=metadata["isDomain"],
                                             metadata=[
                                                 ContextMetadata(
                                                     type=md["type"],
                                                     name=md["name"],
                                                     value=md["value"])
                                                 for md in metadata["metadata"]
                                             ]))

        return DiscoverContextAvailabilityResponse(
            errorCode=200,
            contextRegistrationResponseList=[
                ContextRegistrationResponse(
                    contextRegistration=ContextRegistration(
                        entityIdList=entity_ids,
                        contextRegistrationAttributeList=context_attributes))
            ])
    def registerContext(self, registerContextRequest):

        registrationId = ''.join([choice(digits) for _ in range(16)])

        for contextRegistration in registerContextRequest.contextRegistrationList:
            for entityId in contextRegistration.entityIdList:
                #app_id = entityId.type
                #app_id = app_id and quote(app_id) or self.default_entity_type
                #app_id += "." + quote(entityId.id)
                #app_id = self.id_prefix + app_id
                app_id = quote(entityId.id)

                app = Application(appId=app_id)

                try:
                    scl_app_id = self.scl.create("/applications",
                                                 app).resourceURI
                    scl_containers_id = scl_app_id + "/containers"

                    if contextRegistration.contextRegistrationAttributeList:
                        sleep(1)
                        try:
                            for contextRegistrationAttribute in contextRegistration.contextRegistrationAttributeList:
                                container_id = quote(
                                    contextRegistrationAttribute.name)

                                container = Container(id=container_id)
                                meta_container = Container(id=container_id +
                                                           ".meta")

                                self.scl.create(scl_containers_id, container)
                                scl_meta_container_id = self.scl.create(
                                    scl_containers_id,
                                    meta_container).resourceURI

                                metadata = {
                                    "type":
                                    contextRegistrationAttribute.type,
                                    "isDomain":
                                    contextRegistrationAttribute.isDomain,
                                    "metadata":
                                    self._marshal_metadata(
                                        contextRegistrationAttribute)
                                }

                                self._create_content_instance(
                                    scl_meta_container_id +
                                    "/contentInstances", metadata),

                            sleep(
                                len(contextRegistration.
                                    contextRegistrationAttributeList))
                        except OpenMTCError:
                            #self.scl.delete(scl_app_id)
                            return False
                            raise
                except OpenMTCError as e:
                    return False
                    raise NGSIError(e)

        response = RegisterContextResponse(errorCode=200,
                                           duration=3600 * 24,
                                           registrationId=registrationId)

        response._m2m_path = scl_app_id

        return response
class NGSIInterface(LoggerMixin):
    content_instance_content_type = "application/openmtc-fiware-iot+json"
    id_prefix = "IoT"
    internal_app_id = id_prefix + "openmtc-ngsi"
    _internal_app_id_quoted = quote(internal_app_id)
    default_entity_type = "Generic"
    _last_notification = None

    def get_cfg(self, config, param):
        if param in config:
            return config[param]
        return None

    def __init__(self,
                 scl=None,
                 scl_uri=None,
                 notify_uri=None,
                 config=None,
                 db=None,
                 send_rq=None,
                 *args,
                 **kw):
        super(NGSIInterface, self).__init__(*args, **kw)

        self.notify_uri = notify_uri
        try:
            self.client_secret = config["client_secret"]
            self.client_id = config["client_id"]
            self.username = config["username"]
            self.password = config["password"]
            scl_uri = self.get_cfg(config,
                                   "scl") or scl_uri or "http://localhost:5001"
            self.logger.info("Using %s as scl URI." % (scl_uri))
        except TypeError:
            try:
                config = open(config or "../config.cfg")
                cfg = json.load(config)
                self.client_secret = cfg["client_secret"]
                self.client_id = cfg["client_id"]
                self.username = cfg["username"]
                self.password = cfg["password"]
                scl_uri = self.get_cfg(cfg, "scl") or "http://localhost:5001"
                self.logger.info("Using %s as scl URI." % (scl_uri))

            except Exception as e:
                self.logger.error("Cannot load configuration file: %s" % (e, ))
                raise
        self.access_token = None
        if scl is None:
            scl = MIDClient(scl_uri)
        self.scl = scl

        if send_rq is not None:
            self._send_req = send_rq

        self.db = db or SCLDb(self.scl, self.id_prefix, self, self.name)

        self.xml_writer = NGSIXMLWriter()
        self.json_writer = NGSIJSONWriter()

        self._init_subscriptions()

    def _init_subscriptions(self):
        self._check_subscriptions("/applications")

        apps = self.scl.retrieve("/applications").resource

        for app in apps.applicationCollection:
            if not self.is_internal_app(app.appId):
                #self._check_subscriptions(app["$t"] + "/containers")
                self._check_subscriptions("/applications/" + app.appId +
                                          "/containers")

    def _check_subscriptions(self, path):
        path += "/subscriptions"
        subscriptions = self.scl.retrieve(path).resource
        for subscription in subscriptions.subscriptionCollection:
            p = path + "/" + subscription.id
            subscription = self.scl.retrieve(p).resource
            if subscription.contact == self.notify_uri:
                return

        self._subscribe(path)

    def _subscribe(self, path):
        if not path.endswith("/subscriptions"):
            path += "/subscriptions"
        self.scl.create(path, Subscription(contact=self.notify_uri))

    def is_internal_app(self, app_id):
        return app_id in (self._internal_app_id_quoted, self.internal_app_id)

    def split_app_id(self, app_id):
        app_id = unquote(app_id)

        entity_type, entity_id = "Generic", app_id

        if app_id.startswith(self.id_prefix):
            app_id = app_id[len(self.id_prefix):]
            try:
                entity_type, entity_id = app_id.split(".")
            except ValueError:
                pass

        return entity_type, entity_id

    def _get_appdata(self):
        scl_applications = self.scl.retrieve("/applications").resource

        app_data = []
        for scl_app_reference in scl_applications.applicationCollection:
            if self.is_internal_app(scl_app_reference.appId):
                continue

            entity_type, entity_id = self.split_app_id(scl_app_reference.appId)

            app_data.append((entity_type, entity_id,
                             '/applications/' + scl_app_reference.appId))

        return app_data

    def _get_relevant_apps(self, request):
        return self._get_relevant_apps_dict(request).values()

    def _get_relevant_apps_dict(self, request):
        if not request.entityIdList:
            raise InvalidRequest("entityIdList missing in request.")

        app_data = self._get_appdata()

        relevant_apps = {}

        for entityId in request.entityIdList:
            for entity_type, entity_id, path in app_data:
                if not entityId.type or entityId.type == entity_type:
                    if entityId.isPattern:
                        if re.match(entityId.id, entity_id):
                            relevant_apps[entityId] = (entity_type, entity_id,
                                                       path)
                    elif not entityId.id or entityId.id == entity_id:
                        relevant_apps[entityId] = (entity_type, entity_id,
                                                   path)
        return relevant_apps

    def id_matches(self, entity_type, is_pattern, entity_id, app_type, app_id):
        if entity_type and entity_type != app_type:
            return False
        if is_pattern:
            return bool(re.match(entity_id, app_id))
        return not entity_id or entity_id == app_id

    def _get_relevant_app(self, entityId, app_data):
        for entity_type, entity_id, path in app_data:
            self.logger.debug("---- check %s %s", entity_type, entity_id)
            if entityId.id == entity_id:  # and (not entityId.type or entityId.type == entity_type)
                return entity_type, entity_id, path

    def _create_content_instance(self, path, content):
        content_instance_data = {
            "content": {
                "contentType": self.content_instance_content_type,
                "$t": b64encode(dumps(content))
            }
        }
        from openmtc.model import ContentInstance
        ci = ContentInstance(
            content={
                "contentType": self.content_instance_content_type,
                "$t": b64encode(dumps(content))
            })
        request_indication = CreateRequestIndication(path, ci,
                                                     "contentInstance")
        return self.scl.send_request_indication(request_indication)

    create_content_instance = _create_content_instance

    def _marshal_metadata(self, entity):
        if not entity.metadata:
            return []

        return [{
            "name": md.name or "",
            "type": md.type or "",
            "value": md.value or ""
        } for md in entity.metadata]

    def _parse_metadata(self, meta_data):
        return [
            ContextMetadata(name=md["name"],
                            type=md["type"],
                            value=md["value"]) for md in meta_data
        ]

    def _get_latest_instance(self, content_instances):
        latest = content_instances["latest"]
        latest_id = latest["id"]
        for content_instance in content_instances["contentInstanceCollection"][
                "contentInstance"]:
            if latest_id == content_instance["id"]:
                return content_instance
        raise Exception("Latest instance (%s) not found in collection." %
                        (latest, ))

    def get_content_instances(self, path):
        if not path.endswith("/contentInstances"):
            path += "/contentInstances"

        content_instances = self.scl.retrieve(path).resource

        for content_instance in content_instances.contentInstanceCollection:
            try:
                data = content_instance.content["$t"]
            except KeyError:
                data = content_instance.content["binaryContent"]
            try:
                data = b64decode(data)
                data = loads(data)
            except:
                self.logger.exception("Could not decode content: %s" %
                                      (data, ))

            yield (content_instance, data)

    def _get_access_token(self, url):
        """Returns the current access token.

        Generates a new access token if no current access token can be found"""
        if self.access_token:
            return self.access_token
        data = "client_id=%s&client_secret=%s&grant_type=password&username=%s&password=%s&scope=write" %\
            (self.client_id, self.client_secret, self.username, self.password)

        parsed = urlparse(url)
        path = urlunparse(
            ParseResult(parsed.scheme, parsed.netloc, "/oauth2/access_token",
                        None, None, None))

        auth_resp = urlopen(Request(path, data), timeout=10)
        if auth_resp.getcode() != 200:
            self.logger.error("Error with client credentials")
            return self.access_token
        auth_resp_data = json.loads(auth_resp.read())

        if "access_token" in auth_resp_data:
            self.access_token = auth_resp_data["access_token"]
        else:
            self.logger.error("Error with client credentials")
        return self.access_token

    def _send_req(self, notify_request, content_type, subscription):
        """Sends a notify request to the Event Service with a given access token.

        If the access token has expired, this function calls _get_access_token that generates a new one"""
        if content_type == "json":
            feed = urlopen(Request(
                subscription["reference"],
                self.json_writer.serialize(notify_request), {
                    "Content-Type":
                    "application/json",
                    "Authorization":
                    "OAuth " +
                    self._get_access_token(subscription["reference"])
                }),
                           timeout=10)
            if feed.getcode() == 401:
                # Access_token has expired
                self.access_token = None
                feed = urlopen(Request(
                    subscription["reference"],
                    self.json_writer.serialize(notify_request), {
                        "Content-Type":
                        "application/json",
                        "Authorization":
                        "OAuth " +
                        self._get_access_token(subscription["reference"])
                    }),
                               timeout=10)

        else:
            feed = urlopen(Request(subscription["reference"],
                                   self.xml_writer.serialize(notify_request),
                                   {"Content-Type": "application/xml"}),
                           timeout=10)
        return feed

    def _send_notifications(self, subscriptions, notify_request):
        """Pushes content to the event service.

        First tries to update the event,
        if it is impossible, this function creates a new event (APPEND)"""
        for subscription in subscriptions:
            if self.logger.isEnabledFor(DEBUG):
                self.logger.debug("Sending notification: %s",
                                  self.xml_writer.serialize(notify_request))
            else:
                print "Sending notification: %s" % self.xml_writer.serialize(
                    notify_request)
            try:
                notify_request.subscriptionId = subscription["subscriptionId"]
            except AttributeError:
                # req is an update
                try:
                    feed = self._send_req(notify_request, "json", subscription)
                except Exception as e:
                    self.logger.error("Failed to send notification to %s: %s",
                                      subscription["reference"], e)

                try:
                    repload = json.loads(feed.read())
                    respcode = int(
                        repload["contextResponses"][0]["statusCode"]["code"])
                    if respcode == 472 or respcode == 404:
                        self.logger.info(
                            "Update notification failed, trying append")
                        notify_request.updateAction = "APPEND"
                        self._send_req(notify_request, "json", subscription)
                except Exception as e:
                    self.logger.error("Failed to send notification to %s: %s",
                                      subscription["reference"], e)
            else:
                try:
                    self._send_req(notify_request, "xml", subscription)
                except Exception as e:
                    self.logger.error("Failed to send notification to %s: %s",
                                      subscription["reference"], e)

    def handle_notification(self, type, notification):
        self._last_notification = (type, notification)

        if type not in ("applications", "containers"):
            raise InvalidRequest(type)

        singular = type[:-1]

        collection = notification[singular + "Collection"]["namedReference"]
        try:
            latest = collection[-1]
        except IndexError:
            return False

        if ((type == "applications" and self.is_internal_app(latest["id"]))
                or (type == "containers"
                    and unquote(latest["id"]).endswith(":meta"))):
            return False

        getattr(self, "_handle_" + singular)(latest, collection)
        return True

    def _handle_application(self, latest_app, collection):
        self._subscribe(latest_app["$t"] + "/containers")
        app_type = ""
        app_id = latest_app["id"]
        subs = self.db.find_app_subscriptions(app_type, app_id)
        # TODO send notifications
        # TODO build cr
        cr = ContextRegistration(
            entityIdList=[EntityId(type=app_type, id=app_id, isPattern=False)],
            contextRegistrationAttributeList=[],  #ContextRegistrationAttribute],
            registrationMetaData=[],
            providingApplication="eu.fistar.sdcs")
        crr = ContextRegistrationResponse(contextRegistration=cr)
        notify_request = NotifyContextAvailabilityRequest(
            contextRegistrationResponseList=[crr], errorCode=StatusCode(200))
        self._send_notifications(subs, notify_request)
        # TODO alternative: send a RegisterContextRequest?
        # register_request = RegisterContextRequest(contextRegistrationList=[ cr ])
        # self._send_notifications(subs, register_request)

    def _handle_container(self, container, collection):
        pass