Skip to content
loc-serv.go 36.4 KiB
Newer Older
/*
 * Copyright (c) 2019
 * InterDigital Communications, Inc.
 * All rights reserved.
 *
 * The information provided herein is the proprietary and confidential
 * information of InterDigital Communications, Inc.
 */
package server

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"

	log "github.com/InterDigitalInc/AdvantEDGE/go-apps/meep-loc-serv/log"
	db "github.com/InterDigitalInc/AdvantEDGE/go-apps/meep-loc-serv/redis"
	sbi "github.com/InterDigitalInc/AdvantEDGE/go-apps/meep-loc-serv/sbi"
	clientNotifOMA "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-loc-serv-notification-client"

	"github.com/KromDaniel/rejonson"
	"github.com/go-redis/redis"
	"github.com/gorilla/mux"
)

const basepathURL = "http://meep-loc-serv/etsi-013/location/v1/"
const moduleLocServ string = "loc-serv"

const typeZone = "zone"
const typeAccessPoint = "accessPoint"
const typeUser = "user"
const typeZonalSubscription = "zonalsubs"
const typeUserSubscription = "usersubs"
const typeZoneStatusSubscription = "zonestatus"

const locServChannel string = moduleLocServ

var nextZonalSubscriptionIdAvailable int
var nextUserSubscriptionIdAvailable int
var nextZoneStatusSubscriptionIdAvailable int
var zonalSubscriptionEnteringMap = map[int]string{}
var zonalSubscriptionLeavingMap = map[int]string{}
var zonalSubscriptionTransferringMap = map[int]string{}
var zonalSubscriptionMap = map[int]string{}
var userSubscriptionEnteringMap = map[int]string{}
var userSubscriptionLeavingMap = map[int]string{}
var userSubscriptionTransferringMap = map[int]string{}
var userSubscriptionMap = map[int]string{}
var zoneStatusSubscriptionMap = map[int]*ZoneStatusCheck{}

type ZoneStatusCheck struct {
	ZoneId                 string
	Serviceable            bool
	Unserviceable          bool
	Unknown                bool
	NbUsersInZoneThreshold int
	NbUsersInAPThreshold   int
}

var pubsub *redis.PubSub
var LOC_SERV_DB = 5
var dbClient *rejonson.Client

// Init - Location Service initialization
func Init() (err error) {

	// Connect to Redis DB
	dbClient, err = db.RedisDBConnect(LOC_SERV_DB)
	if err != nil {
		log.Error("Failed connection to Active DB for server. Error: ", err)
		return err
	}
	log.Info("Connected to Active location service DB")

	// Subscribe to Pub-Sub events for MEEP Controller
	// NOTE: Current implementation is RedisDB Pub-Sub
	pubsub, err = db.Subscribe(dbClient, locServChannel)
	if err != nil {
		log.Error("Failed to subscribe to Pub/Sub events. Error: ", err)
		return err
	}

	userTrackingReInit()
	zonalTrafficReInit()

	_ = sbi.Init(updateUserInfo, updateZoneInfo, updateAccessPointInfo)
	return nil
}

// Run - MEEP Location Service execution
func Run() {

	// Listen for subscribed events. Provide event handler method.
	_ = db.Listen(pubsub, eventHandler)
}

func eventHandler(channel string, payload string) {
	// Handle Message according to Rx Channel
	switch channel {

	// MEEP Ctrl Engine active scenario update Channel
	case locServChannel:
		log.Debug("Event received on location service channel in server : ", payload)
		go checkNotificationRegistrations(payload)

	default:
		log.Warn("Unsupported channel")
	}
}

func createClient(notifyPath string) (*clientNotifOMA.APIClient, error) {
	// Create & store client for App REST API
	subsAppClientCfg := clientNotifOMA.NewConfiguration()
	subsAppClientCfg.BasePath = notifyPath
	subsAppClient := clientNotifOMA.NewAPIClient(subsAppClientCfg)
	if subsAppClient == nil {
		log.Error("Failed to create Subscription App REST API client: ", subsAppClientCfg.BasePath)
		err := errors.New("Failed to create Subscription App REST API client")
		return nil, err
	}
	return subsAppClient, nil
}

func deregisterZoneStatus(subsIdStr string) {
	subsId, _ := strconv.Atoi(subsIdStr)
	zonalSubscriptionMap[subsId] = ""
}

func registerZoneStatus(zoneId string, nbOfUsersZoneThreshold uint32, nbOfUsersAPThreshold uint32, opStatus []OperationStatus, subsIdStr string) {

	subsId, _ := strconv.Atoi(subsIdStr)

	var zoneStatus ZoneStatusCheck
	if opStatus != nil {
		for i := 0; i < len(opStatus); i++ {
			switch opStatus[i] {
			case SERVICEABLE:
				zoneStatus.Serviceable = true
			case UNSERVICEABLE:
				zoneStatus.Unserviceable = true
			case OPSTATUS_UNKNOWN:
				zoneStatus.Unknown = true
			default:
			}
		}
	}
	zoneStatus.NbUsersInZoneThreshold = (int)(nbOfUsersZoneThreshold)
	zoneStatus.NbUsersInAPThreshold = (int)(nbOfUsersAPThreshold)
	zoneStatus.ZoneId = zoneId

	zoneStatusSubscriptionMap[subsId] = &zoneStatus
}

func deregisterZonal(subsIdStr string) {
	subsId, _ := strconv.Atoi(subsIdStr)
	zonalSubscriptionMap[subsId] = ""
	zonalSubscriptionEnteringMap[subsId] = ""
	zonalSubscriptionLeavingMap[subsId] = ""
	zonalSubscriptionTransferringMap[subsId] = ""
func registerZonal(zoneId string, event []UserEventType, subsIdStr string) {

	subsId, _ := strconv.Atoi(subsIdStr)

	if event != nil {
		for i := 0; i < len(event); i++ {
			switch event[i] {
			case ENTERING:
				zonalSubscriptionEnteringMap[subsId] = zoneId
				zonalSubscriptionLeavingMap[subsId] = zoneId
			case TRANSFERRING:
				zonalSubscriptionTransferringMap[subsId] = zoneId
		zonalSubscriptionEnteringMap[subsId] = zoneId
		zonalSubscriptionLeavingMap[subsId] = zoneId
		zonalSubscriptionTransferringMap[subsId] = zoneId
	}
	zonalSubscriptionMap[subsId] = zoneId
}

func deregisterUser(subsIdStr string) {
	subsId, _ := strconv.Atoi(subsIdStr)
	userSubscriptionMap[subsId] = ""
	userSubscriptionEnteringMap[subsId] = ""
	userSubscriptionLeavingMap[subsId] = ""
	userSubscriptionTransferringMap[subsId] = ""
func registerUser(userAddress string, event []UserEventType, subsIdStr string) {

	subsId, _ := strconv.Atoi(subsIdStr)

	if event != nil {
		for i := 0; i < len(event); i++ {
			switch event[i] {
			case ENTERING:
				userSubscriptionEnteringMap[subsId] = userAddress
				userSubscriptionLeavingMap[subsId] = userAddress
			case TRANSFERRING:
				userSubscriptionTransferringMap[subsId] = userAddress
		userSubscriptionEnteringMap[subsId] = userAddress
		userSubscriptionLeavingMap[subsId] = userAddress
		userSubscriptionTransferringMap[subsId] = userAddress
	}
	userSubscriptionMap[subsId] = userAddress
}

func checkNotificationRegistrations(payload string) {
	values := strings.Split(payload, ":")
	if len(values) == 5 {
		//value is split in 5 newZoneId:oldZoneId:newAccessPointId:oldAccessPointId:userAddress
		checkNotificationRegisteredUsers(values[0], values[1], values[2], values[3], values[4])
		checkNotificationRegisteredZones(values[0], values[1], values[2], values[3], values[4])
	} else {
		if len(values) == 4 {
			//value is split in 4 zoneId:accessPointId:nbUsersInAP:nbUsersInZone
			checkNotificationRegisteredZoneStatus(values[0], values[1], values[2], values[3])
		}
	}
}

func checkNotificationRegisteredZoneStatus(zoneId string, apId string, nbUsersInAPStr string, nbUsersInZoneStr string) {

	//check all that applies
	for subsId, zoneStatus := range zoneStatusSubscriptionMap {
		if zoneStatus.ZoneId == zoneId {

			nbUsersInZone := 0
			nbUsersInAP := -1
			zoneWarning := false
			apWarning := false
			if nbUsersInZoneStr != "" {
				nbUsersInZone, _ = strconv.Atoi(nbUsersInZoneStr)
				if nbUsersInZone >= zoneStatus.NbUsersInZoneThreshold {
					zoneWarning = true
				}
			}
			if nbUsersInAPStr != "" {
				nbUsersInAP, _ = strconv.Atoi(nbUsersInAPStr)
				if nbUsersInAP >= zoneStatus.NbUsersInAPThreshold {
					apWarning = true
				}
			}

			if zoneWarning || apWarning {
				subsIdStr := strconv.Itoa(subsId)
				jsonInfo := db.DbJsonGet(dbClient, subsIdStr, moduleLocServ+":"+typeZoneStatusSubscription)
				if jsonInfo == "" {
					return
				}

				subscription := convertJsonToZoneStatusSubscription(jsonInfo)

				var zoneStatusNotif clientNotifOMA.ZoneStatusNotification
				zoneStatusNotif.ZoneId = zoneId
				if apWarning {
					zoneStatusNotif.AccessPointId = apId
					zoneStatusNotif.NumberOfUsersInAP = (uint32)(nbUsersInAP)
				}
				if zoneWarning {
					zoneStatusNotif.NumberOfUsersInZone = (uint32)(nbUsersInZone)
				}
				zoneStatusNotif.Timestamp = time.Now().String()
				go sendStatusNotification(subscription.CallbackReference.NotifyURL, context.TODO(), subsIdStr, zoneStatusNotif)
				if apWarning {
					log.Info("Zone Status Notification" + "(" + subsIdStr + "): " + "For event in zone " + zoneId + " which has " + nbUsersInAPStr + " users in AP " + apId)
				} else {
					log.Info("Zone Status Notification" + "(" + subsIdStr + "): " + "For event in zone " + zoneId + " which has " + nbUsersInZoneStr + " users in total")
				}
			}

		}
	}
}

func checkNotificationRegisteredUsers(oldZoneId string, newZoneId string, oldApId string, newApId string, userId string) {

	//check all that applies
	for subsId, value := range userSubscriptionMap {
		if value == userId {
			subsIdStr := strconv.Itoa(subsId)
			jsonInfo := db.DbJsonGet(dbClient, subsIdStr, moduleLocServ+":"+typeUserSubscription)
			if jsonInfo == "" {
				return
			}
			subscription := convertJsonToUserSubscription(jsonInfo)
			var zonal clientNotifOMA.TrackingNotification
			zonal.Address = userId
			zonal.Timestamp = time.Now().String()
			zonal.CallbackData = subscription.ClientCorrelator

			if newZoneId != oldZoneId {
				if userSubscriptionEnteringMap[subsId] != "" {
					zonal.ZoneId = newZoneId
					zonal.CurrentAccessPointId = newApId
					zonal.UserEventType = "ENTERING"
					go sendNotification(subscription.CallbackReference.NotifyURL, context.TODO(), subsIdStr, zonal)
					log.Info("User Notification" + "(" + subsIdStr + "): " + "Entering event in zone " + newZoneId + " for user " + userId)
				}
				if oldZoneId != "" {
					if userSubscriptionLeavingMap[subsId] != "" {
						zonal.ZoneId = oldZoneId
						zonal.CurrentAccessPointId = oldApId
						zonal.UserEventType = "LEAVING"
						go sendNotification(subscription.CallbackReference.NotifyURL, context.TODO(), subsIdStr, zonal)
						log.Info("User Notification" + "(" + subsIdStr + "): " + "Leaving event in zone " + oldZoneId + " for user " + userId)
					}
				}
			} else {
				if newApId != oldApId {
					if userSubscriptionTransferringMap[subsId] != "" {
						zonal.ZoneId = newZoneId
						zonal.CurrentAccessPointId = newApId
						zonal.PreviousAccessPointId = oldApId
						zonal.UserEventType = "TRANSFERRING"
						go sendNotification(subscription.CallbackReference.NotifyURL, context.TODO(), subsIdStr, zonal)
						log.Info("User Notification" + "(" + subsIdStr + "): " + " Transferring event within zone " + newZoneId + " for user " + userId + " from Ap " + oldApId + " to " + newApId)
					}
				}
func sendNotification(notifyUrl string, ctx context.Context, subscriptionId string, notification clientNotifOMA.TrackingNotification) {
	client, err := createClient(notifyUrl)
	if err != nil {
		log.Error(err)
		return
	}

	_, _ = client.NotificationsApi.TrackingNotification(ctx, subscriptionId, notification)
}

func sendStatusNotification(notifyUrl string, ctx context.Context, subscriptionId string, notification clientNotifOMA.ZoneStatusNotification) {
	client, err := createClient(notifyUrl)
	if err != nil {
		log.Error(err)
		return
	}

	_, _ = client.NotificationsApi.ZoneStatusNotification(ctx, subscriptionId, notification)
}

func checkNotificationRegisteredZones(oldZoneId string, newZoneId string, oldApId string, newApId string, userId string) {

	//check all that applies
	for subsId, value := range zonalSubscriptionMap {

		if value == newZoneId {

			if newZoneId != oldZoneId {

				if zonalSubscriptionEnteringMap[subsId] != "" {
					subsIdStr := strconv.Itoa(subsId)
					jsonInfo := db.DbJsonGet(dbClient, subsIdStr, moduleLocServ+":"+typeZonalSubscription)
					if jsonInfo != "" {
						subscription := convertJsonToZonalSubscription(jsonInfo)

						var zonal clientNotifOMA.TrackingNotification
						zonal.ZoneId = newZoneId
						zonal.CurrentAccessPointId = newApId
						zonal.Address = userId
						zonal.UserEventType = "ENTERING"
						zonal.Timestamp = time.Now().String()
						zonal.CallbackData = subscription.ClientCorrelator
						go sendNotification(subscription.CallbackReference.NotifyURL, context.TODO(), subsIdStr, zonal)
						log.Info("Zonal Notify Entering event in zone " + newZoneId + " for user " + userId)
					}
				}
			} else {
				if newApId != oldApId {
					if zonalSubscriptionTransferringMap[subsId] != "" {
						subsIdStr := strconv.Itoa(subsId)
						jsonInfo := db.DbJsonGet(dbClient, subsIdStr, moduleLocServ+":"+typeZonalSubscription)
						if jsonInfo != "" {
							subscription := convertJsonToZonalSubscription(jsonInfo)

							var zonal clientNotifOMA.TrackingNotification
							zonal.ZoneId = newZoneId
							zonal.CurrentAccessPointId = newApId
							zonal.PreviousAccessPointId = oldApId
							zonal.Address = userId
							zonal.UserEventType = "TRANSFERRING"
							zonal.Timestamp = time.Now().String()
							zonal.CallbackData = subscription.ClientCorrelator
							go sendNotification(subscription.CallbackReference.NotifyURL, context.TODO(), subsIdStr, zonal)
							log.Info("Zonal Notify Transferring event in zone " + newZoneId + " for user " + userId + " from Ap " + oldApId + " to " + newApId)
						}
					}
				}
		} else {
			if value == oldZoneId {
				if zonalSubscriptionLeavingMap[subsId] != "" {
					subsIdStr := strconv.Itoa(subsId)
					jsonInfo := db.DbJsonGet(dbClient, subsIdStr, moduleLocServ+":"+typeZonalSubscription)
					if jsonInfo != "" {

						subscription := convertJsonToZonalSubscription(jsonInfo)

						var zonal clientNotifOMA.TrackingNotification
						zonal.ZoneId = oldZoneId
						zonal.CurrentAccessPointId = oldApId
						zonal.Address = userId
						zonal.UserEventType = "LEAVING"
						zonal.Timestamp = time.Now().String()
						zonal.CallbackData = subscription.ClientCorrelator
						go sendNotification(subscription.CallbackReference.NotifyURL, context.TODO(), subsIdStr, zonal)
						log.Info("Zonal Notify Leaving event in zone " + oldZoneId + " for user " + userId)
					}
				}
			}
		}
	}
}

func usersGet(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	u, _ := url.Parse(r.URL.String())
	log.Info("url: ", u.RequestURI())
	q := u.Query()
	zoneIdVar := q.Get("zoneId")
	accessPointIdVar := q.Get("accessPointId")

	var userList UserList

	_ = db.DbJsonGetList(dbClient, zoneIdVar, accessPointIdVar, moduleLocServ+":"+typeUser, populateUserList, &userList)

	userList.ResourceURL = basepathURL + "users"

	jsonResponse, err := json.Marshal(userList)

	if err != nil {
		log.Error(err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusOK)
	fmt.Fprintf(w, string(jsonResponse))
}

func populateUserList(key string, jsonInfo string, zoneId string, apId string, userData interface{}) error {

	userList := userData.(*UserList)
	var userInfo UserInfo

	// Format response
	err := json.Unmarshal([]byte(jsonInfo), &userInfo)
	if err != nil {
		return err
	}
	found1 := false
	found2 := false
	if zoneId != "" {
		if userInfo.ZoneId == zoneId {
			found1 = true
		}
	} else {
		found1 = true
	}
	if apId != "" {
		if userInfo.AccessPointId == apId {
			found2 = true
		}
	} else {
		found2 = true
	}
	if found1 && found2 {
		userList.User = append(userList.User, userInfo)
	}
	return nil
}

func usersGetById(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	vars := mux.Vars(r)

	jsonUserInfo := db.DbJsonGet(dbClient, vars["userId"], moduleLocServ+":"+typeUser)

	if jsonUserInfo != "" {
		fmt.Fprintf(w, jsonUserInfo)

	} else {
		w.WriteHeader(http.StatusNotFound)
		return
	}

	w.WriteHeader(http.StatusOK)
}

func zonesByIdGetAps(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	u, _ := url.Parse(r.URL.String())
	log.Info("url: ", u.RequestURI())
	q := u.Query()
	interestRealm := q.Get("interestRealm")

	var apList AccessPointList

	vars := mux.Vars(r)

	_ = db.DbJsonGetList(dbClient, interestRealm, "", moduleLocServ+":"+typeZone+":"+vars["zoneId"], populateApList, &apList)

	apList.ZoneId = vars["zoneId"]
	apList.ResourceURL = basepathURL + "zones/" + vars["zoneId"] + "/accessPoints"

	jsonResponse, err := json.Marshal(apList)

	if err != nil {
		log.Error(err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusOK)
	fmt.Fprintf(w, string(jsonResponse))
}

func zonesByIdGetApsById(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	vars := mux.Vars(r)

	jsonInfo := db.DbJsonGet(dbClient, vars["accessPointId"], moduleLocServ+":"+typeZone+":"+vars["zoneId"]+":"+typeAccessPoint)

	if jsonInfo != "" {
		fmt.Fprintf(w, jsonInfo)

	} else {
		w.WriteHeader(http.StatusNotFound)
		return
	}

	w.WriteHeader(http.StatusOK)
}

func zonesGet(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	var zoneList ZoneList

	_ = db.DbJsonGetList(dbClient, "", "", moduleLocServ+":"+typeZone, populateZoneList, &zoneList)

	zoneList.ResourceURL = basepathURL + "zones"
	jsonResponse, err := json.Marshal(zoneList)

	if err != nil {
		log.Error(err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusOK)
	fmt.Fprintf(w, string(jsonResponse))
}

func zonesGetById(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	vars := mux.Vars(r)

	jsonInfo := db.DbJsonGet(dbClient, vars["zoneId"], moduleLocServ+":"+typeZone)

	if jsonInfo != "" {
		fmt.Fprintf(w, jsonInfo)

	} else {
		w.WriteHeader(http.StatusNotFound)
		return
	}

	w.WriteHeader(http.StatusOK)
}

func populateZoneList(key string, jsonInfo string, dummy1 string, dummy2 string, userData interface{}) error {

	zoneList := userData.(*ZoneList)
	var zoneInfo ZoneInfo

	// Format response
	err := json.Unmarshal([]byte(jsonInfo), &zoneInfo)
	if err != nil {
		return err
	}
	if zoneInfo.ZoneId != "" {
		zoneList.Zone = append(zoneList.Zone, zoneInfo)
	}
	return nil
}

func populateApList(key string, jsonInfo string, interestRealm string, dummy string, userData interface{}) error {

	apList := userData.(*AccessPointList)
	var apInfo AccessPointInfo

	// Format response
	err := json.Unmarshal([]byte(jsonInfo), &apInfo)
	if err != nil {
		return err
	}
	if apInfo.AccessPointId != "" {
		found := false
		if interestRealm != "" {
			if apInfo.InterestRealm == interestRealm {
				found = true
			}
		} else {
			found = true
		}
		if found {
			apList.AccessPoint = append(apList.AccessPoint, apInfo)
		}
	}
	return nil
}

func userTrackingSubDelById(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	vars := mux.Vars(r)

	err := db.DbJsonDelete(dbClient, vars["subscriptionId"], moduleLocServ+":"+typeUserSubscription)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	deregisterUser(vars["subscriptionId"])

	w.WriteHeader(http.StatusOK)
}

func userTrackingSubGet(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	var userList InlineResponse2001NotificationSubscriptionList

	_ = db.DbJsonGetList(dbClient, "", "", moduleLocServ+":"+typeUserSubscription, populateUserTrackingList, &userList)

	userList.ResourceURL = basepathURL + "subscriptions/userTracking"
	jsonResponse, err := json.Marshal(userList)

	if err != nil {
		log.Error(err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusOK)
	fmt.Fprintf(w, string(jsonResponse))
}

func userTrackingSubGetById(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	vars := mux.Vars(r)

	jsonUserInfo := db.DbJsonGet(dbClient, vars["subscriptionId"], moduleLocServ+":"+typeUserSubscription)

	if jsonUserInfo != "" {
		fmt.Fprintf(w, jsonUserInfo)

	} else {
		w.WriteHeader(http.StatusNotFound)
		return
	}

	w.WriteHeader(http.StatusOK)
}

func userTrackingSubPost(w http.ResponseWriter, r *http.Request) {

	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	subs := new(UserTrackingSubscription)

	decoder := json.NewDecoder(r.Body)
	err := decoder.Decode(&subs)

	if err != nil {
		log.Error(err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	newSubsId := nextUserSubscriptionIdAvailable
	nextUserSubscriptionIdAvailable++
	subsIdStr := strconv.Itoa(newSubsId)
	registerUser(subs.Address, subs.UserEventCriteria, subsIdStr)
	subs.ResourceURL = basepathURL + "subscriptions/userTracking/" + subsIdStr

	_ = db.DbJsonSet(dbClient, subsIdStr, convertUserSubscriptionToJson(subs), moduleLocServ+":"+typeUserSubscription)

	jsonResponse, err := json.Marshal(subs)
	if err != nil {
		log.Error(err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusOK)
	fmt.Fprintf(w, string(jsonResponse))

}

func userTrackingSubPutById(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	vars := mux.Vars(r)

	subs := new(UserTrackingSubscription)

	decoder := json.NewDecoder(r.Body)
	err := decoder.Decode(&subs)

	if err != nil {
		log.Error(err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}

	subsIdStr := vars["subscriptionId"]
	subs.ResourceURL = basepathURL + "subscriptions/userTracking/" + subsIdStr

	_ = db.DbJsonSet(dbClient, subsIdStr, convertUserSubscriptionToJson(subs), moduleLocServ+":"+typeUserSubscription)

	deregisterUser(subsIdStr)
	registerUser(subs.Address, subs.UserEventCriteria, subsIdStr)

	w.WriteHeader(http.StatusOK)
}

func populateUserTrackingList(key string, jsonInfo string, dummy1 string, dummy2 string, userData interface{}) error {

	userList := userData.(*InlineResponse2001NotificationSubscriptionList)
	var userInfo UserTrackingSubscription

	// Format response
	err := json.Unmarshal([]byte(jsonInfo), &userInfo)
	if err != nil {
		return err
	}
	userList.UserTrackingSubscription = append(userList.UserTrackingSubscription, userInfo)
	return nil
}

func zonalTrafficSubDelById(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	vars := mux.Vars(r)

	err := db.DbJsonDelete(dbClient, vars["subscriptionId"], moduleLocServ+":"+typeZonalSubscription)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	deregisterZonal(vars["subscriptionId"])
	w.WriteHeader(http.StatusOK)
}

func zonalTrafficSubGet(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	var zoneList InlineResponse200NotificationSubscriptionList

	_ = db.DbJsonGetList(dbClient, "", "", moduleLocServ+":"+typeZonalSubscription, populateZonalTrafficList, &zoneList)

	zoneList.ResourceURL = basepathURL + "subcription/zonalTraffic"
	jsonResponse, err := json.Marshal(zoneList)

	if err != nil {
		log.Error(err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusOK)
	fmt.Fprintf(w, string(jsonResponse))
}

func zonalTrafficSubGetById(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	vars := mux.Vars(r)

	jsonInfo := db.DbJsonGet(dbClient, vars["subscriptionId"], moduleLocServ+":"+typeZonalSubscription)
	if jsonInfo != "" {
		fmt.Fprintf(w, jsonInfo)

	} else {
		w.WriteHeader(http.StatusNotFound)
		return
	}

	w.WriteHeader(http.StatusOK)
}

func zonalTrafficSubPost(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	subs := new(ZonalTrafficSubscription)

	decoder := json.NewDecoder(r.Body)
	err := decoder.Decode(&subs)

	if err != nil {
		log.Error(err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	newSubsId := nextZonalSubscriptionIdAvailable
	nextZonalSubscriptionIdAvailable++
	subsIdStr := strconv.Itoa(newSubsId)
	/*
		if subs.Duration > 0 {
			//TODO start a timer mecanism and expire subscription
		}
		//else, lasts forever or until subscription is deleted
	*/
	if subs.Duration != "" && subs.Duration != "0" {
		//TODO start a timer mecanism and expire subscription
		log.Info("Non zero duration")
	}
	//else, lasts forever or until subscription is deleted

	subs.ResourceURL = basepathURL + "subscriptions/zonalTraffic/" + subsIdStr

	_ = db.DbJsonSet(dbClient, subsIdStr, convertZonalSubscriptionToJson(subs), moduleLocServ+":"+typeZonalSubscription)

	registerZonal(subs.ZoneId, subs.UserEventCriteria, subsIdStr)

	jsonResponse, err := json.Marshal(subs)
	if err != nil {
		log.Error(err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusOK)
	fmt.Fprintf(w, string(jsonResponse))

}

func zonalTrafficSubPutById(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	vars := mux.Vars(r)

	subs := new(ZonalTrafficSubscription)

	decoder := json.NewDecoder(r.Body)
	err := decoder.Decode(&subs)

	if err != nil {
		log.Error(err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	subsIdStr := vars["subscriptionId"]
	subs.ResourceURL = basepathURL + "subscriptions/zonalTraffic/" + subsIdStr

	_ = db.DbJsonSet(dbClient, subsIdStr, convertZonalSubscriptionToJson(subs), moduleLocServ+":"+typeZonalSubscription)

	deregisterZonal(subsIdStr)
	registerZonal(subs.ZoneId, subs.UserEventCriteria, subsIdStr)

	w.WriteHeader(http.StatusOK)
}

func populateZonalTrafficList(key string, jsonInfo string, dummy1 string, dummy2 string, userData interface{}) error {

	zoneList := userData.(*InlineResponse200NotificationSubscriptionList)
	var zoneInfo ZonalTrafficSubscription

	// Format response
	err := json.Unmarshal([]byte(jsonInfo), &zoneInfo)
	if err != nil {
		return err
	}
	zoneList.ZonalTrafficSubscription = append(zoneList.ZonalTrafficSubscription, zoneInfo)
	return nil
}

func zoneStatusDelById(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	vars := mux.Vars(r)

	err := db.DbJsonDelete(dbClient, vars["subscriptionId"], moduleLocServ+":"+typeZoneStatusSubscription)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	deregisterZoneStatus(vars["subscriptionId"])

	w.WriteHeader(http.StatusOK)
}

func zoneStatusGet(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	var zoneList InlineResponse2002NotificationSubscriptionList

	_ = db.DbJsonGetList(dbClient, "", "", moduleLocServ+":"+typeZoneStatusSubscription, populateZoneStatusList, &zoneList)

	zoneList.ResourceURL = basepathURL + "subscription/zoneStatus"
	jsonResponse, err := json.Marshal(zoneList)

	if err != nil {
		log.Error(err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusOK)
	fmt.Fprintf(w, string(jsonResponse))
}

func zoneStatusGetById(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	vars := mux.Vars(r)

	jsonInfo := db.DbJsonGet(dbClient, vars["subscriptionId"], moduleLocServ+":"+typeZoneStatusSubscription)

	if jsonInfo != "" {
		fmt.Fprintf(w, jsonInfo)

	} else {
		w.WriteHeader(http.StatusNotFound)
		return
	}

	w.WriteHeader(http.StatusOK)
}

func zoneStatusPost(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	subs := new(ZoneStatusSubscription)

	decoder := json.NewDecoder(r.Body)
	err := decoder.Decode(&subs)

	if err != nil {
		log.Error(err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	newSubsId := nextZoneStatusSubscriptionIdAvailable
	nextZoneStatusSubscriptionIdAvailable++
	subsIdStr := strconv.Itoa(newSubsId)

	subs.ResourceURL = basepathURL + "subscriptions/zoneStatus/" + subsIdStr

	_ = db.DbJsonSet(dbClient, subsIdStr, convertZoneStatusSubscriptionToJson(subs), moduleLocServ+":"+typeZoneStatusSubscription)

	registerZoneStatus(subs.ZoneId, subs.NumberOfUsersZoneThreshold, subs.NumberOfUsersAPThreshold, subs.OperationStatus, subsIdStr)

	jsonResponse, err := json.Marshal(subs)
	if err != nil {
		log.Error(err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusOK)
}

func zoneStatusPutById(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")