text: # Copyright (C) 2019 Verizon. All Rights Reserved
  #
  # Licensed 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 ast
  import json
  import logging
  import uuid
  from collections import Counter
  
  import requests
  from rest_framework import status
  
  from catalog.packages import const
  from catalog.pub.database.models import NsdmSubscriptionModel
  from catalog.pub.exceptions import CatalogException, \
      NsdmBadRequestException, NsdmDuplicateSubscriptionException, SubscriptionDoesNotExistsException
  from catalog.pub.utils.values import ignore_case_get
  
  logger = logging.getLogger(__name__)
  
  PARAMSBASICKEYS = ["userName", "password"]
  
  PARAMSOAUTH2CLIENTCREDENTIALSKEYS = ["clientId", "clientPassword",
                                       "tokenEndpoint"]
  
  
  def is_filter_type_equal(new_filter, existing_filter):
      return Counter(list(set(new_filter))) == Counter(existing_filter)
  
  
  class NsdmSubscription:
  
      def __init__(self):
          pass
  
      def query_single_subscription(self, subscription_id):
          logger.debug("Start Query Subscription... ")
          subscription = \
              NsdmSubscriptionModel.objects.filter(
                  subscriptionid=subscription_id)
          if not subscription.exists():
              raise SubscriptionDoesNotExistsException(
                  "Subscription(%s) doesn't exist" % subscription_id)
          logger.debug("Subscription found... ")
          return self.fill_resp_data(subscription[0])
  
      def delete_single_subscription(self, subscription_id):
          logger.debug("Start Delete Subscription... ")
          subscription = \
              NsdmSubscriptionModel.objects.filter(
                  subscriptionid=subscription_id)
          if not subscription.exists():
              raise SubscriptionDoesNotExistsException(
                  "Subscription(%s) doesn't exist" % subscription_id)
          subscription.delete()
          logger.debug("Deleted Subscription... ")
  
      def query_multi_subscriptions(self, query_params):
          self.params = query_params
          query_data = {}
          logger.debug("Start QueryMultiSubscriptions get --> "
                       "Check for filters in query params" % self.params)
          for query, value in list(self.params.items()):
              if query in const.NSDM_NOTIFICATION_FILTERS and value:
                  query_data[query + '__icontains'] = json.dumps(list(set(value)))
          # Query the database with filter if the request
          # has fields in request params, else fetch all records
          if query_data:
              subscriptions = NsdmSubscriptionModel.objects.filter(**query_data)
          else:
              subscriptions = NsdmSubscriptionModel.objects.all()
          if not subscriptions.exists():
              raise SubscriptionDoesNotExistsException("Subscriptions doesn't exist")
          return [self.fill_resp_data(subscription)
                  for subscription in subscriptions]
  
      def check_callbackuri_connection(self):
          logger.debug("Create Subscription --> Test Callback URI --"
                       "Sending GET request to %s" % self.callback_uri)
          try:
              response = requests.get(self.callback_uri, timeout=2, verify=False)
              if response.status_code != status.HTTP_204_NO_CONTENT:
                  raise CatalogException("callbackUri %s returns %s status "
                                         "code." % (self.callback_uri,
                                                    response.status_code))
          except Exception:
              raise CatalogException("callbackUri %s didn't return 204 status"
                                     "code." % self.callback_uri)
  
      def fill_resp_data(self, subscription):
          subscription_filter = dict()
          for filter_type in const.NSDM_NOTIFICATION_FILTERS:
              if subscription.__dict__[filter_type]:
                  subscription_filter[filter_type] = \
                      ast.literal_eval(subscription.__dict__[filter_type])
          resp_data = {
              'id': subscription.subscriptionid,
              'callbackUri': subscription.callback_uri,
              'filter': subscription_filter,
              '_links': json.loads(subscription.links)
          }
          return resp_data
  
      def create(self, data):
          logger.debug("Start Create Subscription... ")
          self.filter = ignore_case_get(data, "filter", {})
          self.callback_uri = ignore_case_get(data, "callbackUri")
          self.authentication = ignore_case_get(data, "authentication", {})
          self.subscription_id = str(uuid.uuid4())
          self.check_callbackuri_connection()
          self.check_valid_auth_info()
          self.check_filter_types()
          self.check_valid()
          self.save_db()
          subscription = \
              NsdmSubscriptionModel.objects.get(
                  subscriptionid=self.subscription_id)
          return self.fill_resp_data(subscription)
  
      def check_filter_types(self):
          # Check if both nsdId and nsdInfoId
          # or pnfdId and pnfdInfoId are present
          logger.debug("Create Subscription --> Validating Filters... ")
          if self.filter and \
                  self.filter.get("nsdId", "") and \
                  self.filter.get("nsdInfoId", ""):
              raise NsdmBadRequestException("Notification Filter should contain"
                                            " either nsdId or nsdInfoId")
          if self.filter and \
                  self.filter.get("pnfdId", "") and \
                  self.filter.get("pnfdInfoIds", ""):
              raise NsdmBadRequestException("Notification Filter should contain"
                                            " either pnfdId or pnfdInfoIds")
  
      def check_valid_auth_info(self):
          logger.debug("Create Subscription --> Validating Auth "
                       "details if provided... ")
          if self.authentication.get("paramsBasic", {}) and \
                  const.BASIC not in self.authentication.get("authType", ''):
              raise NsdmBadRequestException('Auth type should be ' + const.BASIC)
          if self.authentication.get("paramsOauth2ClientCredentials", {}) and \
                  const.OAUTH2_CLIENT_CREDENTIALS not in \
                  self.authentication.get("authType", ''):
              raise NsdmBadRequestException('Auth type should '
                                            'be ' + const.OAUTH2_CLIENT_CREDENTIALS)
          if const.BASIC in self.authentication.get("authType", '') and \
                  "paramsBasic" in list(self.authentication.keys()) and \
                  not is_filter_type_equal(PARAMSBASICKEYS, list(
                      self.authentication.get("paramsBasic").keys())):
              raise NsdmBadRequestException('userName and password needed '
                                            'for ' + const.BASIC)
          if const.OAUTH2_CLIENT_CREDENTIALS in \
                  self.authentication.get("authType", '') and \
                  "paramsOauth2ClientCredentials" in \
                  list(self.authentication.keys()) and \
                  not is_filter_type_equal(PARAMSOAUTH2CLIENTCREDENTIALSKEYS, list(
                      self.authentication.get("paramsOauth2ClientCredentials").keys())):
              raise NsdmBadRequestException('clientId, clientPassword and '
                                            'tokenEndpoint required '
                                            'for ' + const.OAUTH2_CLIENT_CREDENTIALS)
  
      def check_filter_exists(self, subscription):
          for filter_type in const.NSDM_NOTIFICATION_FILTERS:
              if not is_filter_type_equal(self.filter.get(filter_type, []),
                                          ast.literal_eval(
                                              getattr(subscription,
                                                      filter_type))):
                  return False
          return True
  
      def check_valid(self):
          logger.debug("Create Subscription --> Checking DB if "
                       "same subscription has already existed... ")
          subscriptions = \
              NsdmSubscriptionModel.objects.filter(
                  callback_uri=self.callback_uri)
  
          for subscription in subscriptions:
              if self.check_filter_exists(subscription):
                  links = json.loads(subscription.links)
                  logger.error(
                      "Subscription has already existed with the "
                      "same callbackUri and filter:%s" % links)
                  raise NsdmDuplicateSubscriptionException(
                      "/%s" % (links["self"]["href"]))
  
      def save_db(self):
          logger.debug("Create Subscription --> Saving the subscription "
                       "%s to the database" % self.subscription_id)
          links = {
              "self": {
                  "href":
                      const.NSDM_SUBSCRIPTION_ROOT_URI + self.subscription_id
              }
          }
          subscription_save_db = {
              "subscriptionid": self.subscription_id,
              "callback_uri": self.callback_uri,
              "auth_info": json.dumps(self.authentication),
              "links": json.dumps(links)
          }
          for filter_type in const.NSDM_NOTIFICATION_FILTERS:
              if self.filter:
                  subscription_save_db[filter_type] = json.dumps(
                      list(set(self.filter.get(filter_type, []))))
          NsdmSubscriptionModel.objects.create(**subscription_save_db)
          logger.debug('Create Subscription[%s] success', self.subscription_id)
  