summaryrefslogtreecommitdiffstats
path: root/signal-composer-binding/signal-composer-apidef.json
blob: cf45aecf2e0987afa20bde6af16339aa2349030d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
{
	"openapi": "3.0.0",
	"$schema": "http://iot.bzh/download/openapi/schema-3.0/default-schema.json",
	"info": {
		"description": "",
		"title": "signals-composer-service",
		"version": "4.0",
		"x-binding-c-generator": {
			"api": "signal-composer",
			"version": 2,
			"prefix": "",
			"postfix": "",
			"start": null ,
			"onevent": "onEvent",
			"preinit": "loadConf",
			"init": "execConf",
			"scope": "",
			"private": false
		}
	},
	"servers": [
		{
		"url": "ws://{host}:{port}/api/monitor",
		"description": "Signals composer API connected to low level AGL services",
		"variables": {
			"host": {
			"default": "localhost"
		},
			"port": {
				"default": "1234"
			}
		},
		"x-afb-events": [
			{
				"$ref": "#/components/schemas/afb-event"
			}
		]
	}
	],
	"components": {
	"schemas": {
		"afb-reply": {
			"$ref": "#/components/schemas/afb-reply-v2"
		},
		"afb-event": {
			"$ref": "#/components/schemas/afb-event-v2"
		},
		"afb-reply-v2": {
			"title": "Generic response.",
			"type": "object",
			"required": [ "jtype", "request" ],
			"properties": {
			"jtype": {
				"type": "string",
				"const": "afb-reply"
			},
			"request": {
				"type": "object",
				"required": [ "status" ],
				"properties": {
					"status": { "type": "string" },
					"info": { "type": "string" },
					"token": { "type": "string" },
					"uuid": { "type": "string" },
					"reqid": { "type": "string" }
				}
			},
			"response": { "type": "object" }
			}
		},
		"afb-event-v2": {
			"type": "object",
			"required": [ "jtype", "event" ],
			"properties": {
				"jtype": {
					"type": "string",
					"const": "afb-event"
				},
				"event": { "type": "string" },
				"data": { "type": "object" }
			}
		}
		},
		"x-permissions": {
			"addObjects": {
				"permission": "urn:AGL:permission::platform:composer:addObjects"
			  }
		},
		"responses": {
			"200": {
				"description": "A complex object array response",
				"content": {
					"application/json": {
						"schema": {
							"$ref": "#/components/schemas/afb-reply"
						}
					}
				}
			}
	}
	},
	"paths": {
		"/subscribe": {
			"description": "Subscribe to a signal object",
			"parameters": [
				{
					"in": "query",
					"name": "event",
					"required": false,
					"schema": { "type": "string" }
				}
			],
			"responses": {
				"200": {"$ref": "#/components/responses/200"}
			}
		},
		"/unsubscribe": {
			"description": "Unsubscribe previously suscribed signal objects.",
			"parameters": [
				{
					"in": "query",
					"name": "event",
					"required": false,
					"schema": { "type": "string" }
				}
			],
			"responses": {
				"200": {"$ref": "#/components/responses/200"}
			}
		},
		"/get": {
			"description": "Get informations about a resource or element",
			"responses": {
				"200": {"$ref": "#/components/responses/200"}
			}
		},
		"/list": {
			"description": "List all signals already configured",
			"responses": {
				"200": {"$ref": "#/components/responses/200"}
			}
		},
		"/addObjects": {
			"description": "Load new objects from an additional config file designated by JSON argument with the key 'file'.",
			"get": {
				"x-permissions": {
				  "$ref": "#/components/x-permissions/addObjects"
				},
				"responses": {
				  "200": {"$ref": "#/components/responses/200"}
				}
			},
			"parameters": [
				{
					"in": "query",
					"name": "path",
					"required": true,
					"schema": { "type": "string"}
				}
			]
		}
	}
}
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ .highlight .ge { font-style: italic } /* Generic.Emph */ .highlight .gr { color: #aa0000 } /* Generic.Error */ .highlight .gh { color: #333333 } /* Generic.Heading */ .highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ .highlight .go { color: #888888 } /* Generic.Output */ .highlight .gp { color: #555555 } /* Generic.Prompt */ .highlight .gs { font-weight: bold } /* Generic.Strong */ .highlight .gu { color: #666666 } /* Generic.Subheading */ .highlight .gt { color: #aa0000 } /* Generic.Traceback */ .highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */ .highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */ .highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */ .highlight .kp { color: #008800 } /* Keyword.Pseudo */ .highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */ .highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */ .highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */ .highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */ .highlight .na { color: #336699 } /* Name.Attribute */ .highlight .nb { color: #003388 } /* Name.Builtin */ .highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */ .highlight .no { color: #003366; font-weight: bold } /* Name.Constant */ .highlight .nd { color: #555555 } /* Name.Decorator */ .highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */ .highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */ .highlight .nl { color: #336699; font-style: italic } /* Name.Label */ .highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */ .highlight .py { color: #336699; font-weight: bold } /* Name.Property */ .highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */ .highlight .nv { color: #336699 } /* Name.Variable */ .highlight .ow { color: #008800 } /* Operator.Word */ .highlight .w { color: #bbbbbb } /* Text.Whitespace */ .highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */ .highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */ .highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ .highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ .highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ .highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */ .highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ .highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ .highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */ .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */ }
/*
 * Copyright (C) 2015, 2016 "IoT.bzh"
 * Author "Romain Forlot" <romain.forlot@iot.bzh>
 *
 * 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.
*/

#include <uuid.h>
#include <string.h>
#include <fnmatch.h>

#include "clientApp.hpp"

extern "C" void searchNsetSignalValueHandle(const char* aName, uint64_t timestamp, struct signalValue value)
{
	std::vector<std::shared_ptr<Signal>> signals = Composer::instance().searchSignals(aName);
	if(!signals.empty())
	{
		for(auto& sig: signals)
			{sig->set(timestamp, value);}
	}
}

extern "C" void setSignalValueHandle(void* aSignal, uint64_t timestamp, struct signalValue value)
{
	Signal* sig = static_cast<Signal*>(aSignal);
	sig->set(timestamp, value);
}

bool startsWith(const std::string& str, const std::string& pattern)
{
	size_t sep;
	if( (sep = str.find(pattern)) != std::string::npos && !sep)
		{return true;}
	return false;
}

void extractString(void* closure, json_object* object)
{
	std::vector<std::string> *files = (std::vector<std::string>*) closure;
	const char *oneFile = json_object_get_string(object);

	files->push_back(oneFile);
}

// aSignal member value will be initialized in sourceAPI->addSignal()
static struct signalCBT pluginHandle = {
	.searchNsetSignalValue = searchNsetSignalValueHandle,
	.setSignalValue = setSignalValueHandle,
	.aSignal = nullptr,
	.pluginCtx = nullptr,
};

CtlSectionT Composer::ctlSections_[] = {
	[0]={.key="plugins" , .uid="plugins", .info=nullptr,
		.loadCB=pluginsLoad,
		.handle=&pluginHandle,
		.actions=nullptr},
	[1]={.key="sources" , .uid="sources", .info=nullptr,
		 .loadCB=loadSourcesAPI,
		 .handle=nullptr,
		 .actions=nullptr},
	[2]={.key="signals" , .uid="signals", .info=nullptr,
		 .loadCB=loadSignals,
		 .handle=nullptr,
		 .actions=nullptr},
	[3]={.key=nullptr, .uid=nullptr, .info=nullptr,
		 .loadCB=nullptr,
		 .handle=nullptr,
		 .actions=nullptr}
};

///////////////////////////////////////////////////////////////////////////////
//                             PRIVATE METHODS                               //
///////////////////////////////////////////////////////////////////////////////

Composer::Composer()
{}

Composer::~Composer()
{
	for(auto& j: ctlActionsJ_)
	{
		json_object_put(j);
	}
	json_object_put(ctlConfig_->configJ);
	json_object_put(ctlConfig_->requireJ);
	free(ctlConfig_);
}

json_object* Composer::buildPluginAction(std::string name, std::string function, json_object* functionArgsJ)
{
	json_object *callbackJ = nullptr, *ctlActionJ = nullptr;
	std::string uri = std::string(function).substr(9);
	std::vector<std::string> uriV = Composer::parseURI(uri);
	if(uriV.size() != 2)
	{
		AFB_ERROR("Miss something in uri either plugin name or function name. Uri has to be like: plugin://<plugin-name>/<function-name>");
		return nullptr;
	}
	wrap_json_pack(&callbackJ, "{ss,ss,so*}",
		"plugin", uriV[0].c_str(),
		"function", uriV[1].c_str(),
		"args", functionArgsJ);
	wrap_json_pack(&ctlActionJ, "{ss,so}",
		"uid", name.c_str(),
		"callback", callbackJ);

	return ctlActionJ;
}

json_object* Composer::buildApiAction(std::string name, std::string function, json_object* functionArgsJ)
{
	json_object *subcallJ = nullptr, *ctlActionJ = nullptr;
	std::string uri = std::string(function).substr(6);
	std::vector<std::string> uriV = Composer::parseURI(uri);
	if(uriV.size() != 2)
	{
		AFB_ERROR("Miss something in uri either plugin name or function name. Uri has to be like: api://<plugin-name>/<function-name>");
		return nullptr;
	}
	wrap_json_pack(&subcallJ, "{ss,ss}",
		"api", uriV[0].c_str(),
		"verb", uriV[1].c_str());
	wrap_json_pack(&ctlActionJ, "{ss,so,so*}",
		"uid", name.c_str(),
		"subcall", subcallJ,
		"args", functionArgsJ);

	return ctlActionJ;
}

json_object* Composer::buildLuaAction(std::string name, std::string function, json_object* functionArgsJ)
{
	json_object *luaJ = nullptr, *ctlActionJ = nullptr;
	std::string fName, filepath;
	std::string uri = std::string(function).substr(6);
	std::vector<std::string> uriV = Composer::parseURI(uri);
	if(uriV.size() > 2)
	{
		int i = 0;
		while(i < uriV.size()-1)
			{filepath += uriV[i] + "/";}
		fName = uriV[-1];
	}
	else if(uriV.size() == 2)
	{
		filepath = uriV[0];
		fName = uriV[2];
	}
	else if(uriV.size() == 1)
		{fName = uriV[0];}
	else
	{
		AFB_ERROR("Missing something in uri either lua filepath or function name. Uri has to be like: lua://file/path/file.lua/function_name with filepath optionnal. If not specified, search will be done in default directories");
		return nullptr;
	}

	wrap_json_pack(&luaJ, "{ss*,ss}",
		"load", filepath.empty() ? NULL:filepath.c_str(),
		"func", fName.c_str());
	wrap_json_pack(&ctlActionJ, "{ss,so,so*}",
		"uid", name.c_str(),
		"lua", luaJ,
		"args", functionArgsJ);

	return ctlActionJ;
}

CtlActionT* Composer::convert2Action(const std::string& name, json_object* actionJ)
{
	json_object *functionArgsJ = nullptr,
				*ctlActionJ = nullptr;
	char *function;
	const char *plugin;
	CtlActionT *ctlAction = new CtlActionT;
	memset(ctlAction, 0, sizeof(CtlActionT));

	if(actionJ &&
		!wrap_json_unpack(actionJ, "{ss,s?s,s?o !}", "function", &function,
			"plugin", &plugin,
			"args", &functionArgsJ))
	{
		if(startsWith(function, "lua://"))
		{
			ctlActionJ = buildLuaAction(name, function, functionArgsJ);
		}
		else if(startsWith(function, "api://"))
		{
			ctlActionJ = buildApiAction(name, function, functionArgsJ);
		}
		else if(startsWith(function, "plugin://"))
		{
			ctlActionJ = buildPluginAction(name, function, functionArgsJ);
		}
		else if(startsWith(function, "builtin://"))
		{
			std::string uri = std::string(function).substr(10);
			std::vector<std::string> uriV = Composer::parseURI(uri);
			if(uriV.size() > 1) {AFB_WARNING("Too many thing specified. Uri has to be like: builtin://<builtin-function-name>");}

			json_object *callbackJ = nullptr;
			wrap_json_pack(&callbackJ, "{ss,ss,so*}",
				"plugin", "builtin",
				"function", uriV[0].c_str(),
				"args", functionArgsJ);
			wrap_json_pack(&ctlActionJ, "{ss,so}",
				"uid", name.c_str(),
				"callback", callbackJ);
		}
		else
		{
			AFB_ERROR("Wrong function uri specified. You have to specified 'lua://', 'plugin://' or 'api://'. (%s)", function);
			return nullptr;
		}
	}

	// Register json object for later release
	ctlActionsJ_.push_back(ctlActionJ);
	if(ctlActionJ)
	{
		int err = ActionLoadOne(nullptr, ctlAction, ctlActionJ, 0);
		if(! err)
			{return ctlAction;}
	}

	return nullptr;
}

/// @brief Add the builtin plugin in the default plugins section definition
///
/// @param[in] section - Control Section structure
/// @param[in] pluginsJ - JSON object containing all plugins definition made in
///  JSON configuration file.
///
/// @return 0 if OK, other if not.
int Composer::pluginsLoad(AFB_ApiT apiHandle, CtlSectionT *section, json_object *pluginsJ)
{
	json_object* builtinJ = nullptr, * completePluginsJ = nullptr;

	if(pluginsJ)
	{
		wrap_json_pack(&builtinJ, "{ss,ss,ss,ss,s[s]}",
			"uid", "builtin",
			"ldpath", CONTROL_PLUGIN_PATH,
			"info", "Builtin routine for onReceived or getSignals routines",
			"basename", "builtin",
			"lua2c", "setSignalValueWrap");

		if (json_object_get_type(pluginsJ) == json_type_array)
		{
				json_object_array_add(pluginsJ, builtinJ);
				completePluginsJ = pluginsJ;
		}
		else
		{

			completePluginsJ = json_object_new_array();
			json_object_array_add(completePluginsJ, pluginsJ);
			json_object_array_add(completePluginsJ, builtinJ);
		}
	}

	return PluginConfig(nullptr, section, completePluginsJ);
}

int Composer::loadOneSourceAPI(json_object* sourceJ)
{
	json_object *initJ = nullptr,
				*getSignalsJ = nullptr,
				*onReceivedJ = nullptr;
	CtlActionT  *initCtl = nullptr,
				*getSignalsCtl = nullptr,
				*onReceivedCtl = nullptr;
	const char *uid, *api, *info;
	int retention = 0;

	int err = wrap_json_unpack(sourceJ, "{ss,s?s,s?o,s?o,s?o,s?i,s?o !}",
			"uid", &uid,
			"api", &api,
			"info", &info,
			"init", &initJ,
			"getSignals", &getSignalsJ,
			// Signals field to make signals conf by sources
			"onReceived", &onReceivedJ,
			"retention", &retention);
	if (err)
	{
		AFB_ERROR("Missing something api|[info]|[init]|[getSignals] in %s", json_object_get_string(sourceJ));
		return err;
	}

	// Checking duplicate entry and ignore if so
	for(auto& src: sourcesListV_)
	{
		if(*src == uid)
		{
			json_object_put(sourceJ);
			return 0;
		}
	}

	if(ctlConfig_ && ctlConfig_->requireJ)
	{
		const char* requireS = json_object_to_json_string(ctlConfig_->requireJ);
		if(!strcasestr(requireS, api))
			{AFB_WARNING("Caution! You don't specify the API source as required in the metadata section. This API '%s' may not be initialized", api);}
	}

	initCtl = initJ ? convert2Action("init", initJ) : nullptr;

	// Define default action to take to subscibe souce's signals if none
	// defined.
	if(!getSignalsJ)
	{
		std::string function = "api://" + std::string(api) + "/subscribe";
		getSignalsJ = buildApiAction("getSignals", function, nullptr);
		// Register json object for later release
		ctlActionsJ_.push_back(getSignalsJ);
	}
	getSignalsCtl = convert2Action("getSignals", getSignalsJ);

	onReceivedCtl = onReceivedJ ? convert2Action("onReceived", onReceivedJ) : nullptr;

	sourcesListV_.push_back(std::make_shared<SourceAPI>(uid, api, info, initCtl, getSignalsCtl, onReceivedCtl, retention));
	return err;
}

int Composer::loadSourcesAPI(AFB_ApiT apihandle, CtlSectionT* section, json_object *sourcesJ)
{
	int err = 0;
	Composer& composer = instance();

	if(sourcesJ)
	{
		json_object *sigCompJ = nullptr;
		// add the signal composer itself as source
		wrap_json_pack(&sigCompJ, "{ss,ss,ss}",
		"uid", "Signal-Composer-service",
		"api", afbBindingV2.api,
		"info", "Api on behalf the virtual signals are sent");

		if(json_object_is_type(sourcesJ, json_type_array))
			json_object_array_add(sourcesJ, sigCompJ);

		if (json_object_get_type(sourcesJ) == json_type_array)
		{
			int count = json_object_array_length(sourcesJ);

			for (int idx = 0; idx < count; idx++)
			{
				json_object *sourceJ = json_object_array_get_idx(sourcesJ, idx);
				err = composer.loadOneSourceAPI(sourceJ);
				if (err) return err;
			}
		}
		else
		{
			if ((err = composer.loadOneSourceAPI(sourcesJ))) return err;
			if (sigCompJ && (err = composer.loadOneSourceAPI(sigCompJ))) return err;
		}
	}
	else
		{Composer::instance().initSourcesAPI();}

	return err;
}

int Composer::loadOneSignal(json_object* signalJ)
{
	json_object *onReceivedJ = nullptr,
				*dependsJ = nullptr,
				*getSignalsArgs = nullptr;
	CtlActionT* onReceivedCtl;
	const char *id = nullptr,
			   *event = nullptr,
			   *unit = nullptr;
	int retention = 0;
	double frequency=0.0;
	std::vector<std::string> dependsV;
	ssize_t sep;
	std::shared_ptr<SourceAPI> src = nullptr;

	int err = wrap_json_unpack(signalJ, "{ss,s?s,s?o,s?o,s?i,s?s,s?F,s?o !}",
			"uid", &id,
			"event", &event,
			"depends", &dependsJ,
			"getSignalsArgs", &getSignalsArgs,
			"retention", &retention,
			"unit", &unit,
			"frequency", &frequency,
			"onReceived", &onReceivedJ);
	if (err)
	{
		AFB_ERROR("Missing something uid|[event|depends]|[getSignalsArgs]|[retention]|[unit]|[frequency]|[onReceived] in %s", json_object_get_string(signalJ));
		return err;
	}

	// event or depends field manadatory
	if( (!event && !dependsJ) || (event && dependsJ) )
	{
		AFB_ERROR("Missing something uid|[event|depends]|[getSignalsArgs]|[retention]|[unit]|[frequency]|[onReceived] in %s. Or you declare event AND depends, only one of them is needed.", json_object_get_string(signalJ));
		return -1;
	}

	// Declare a raw signal
	if(event)
	{
		std::string eventStr = std::string(event);
		if( (sep = eventStr.find("/")) == std::string::npos)
		{
			AFB_ERROR("Missing something in event declaration. Has to be like: <api>/<event>");
			return -1;
		}
		std::string api = eventStr.substr(0, sep);
		src = getSourceAPI(api);
	}
	else
	{
		event = "";
	}
	// Process depends JSON object to declare virtual signal dependencies
	if (dependsJ)
	{
		if(json_object_get_type(dependsJ) == json_type_array)
		{
			int count = json_object_array_length(dependsJ);
			for(int i = 0; i < count; i++)
			{
				std::string sourceStr = json_object_get_string(json_object_array_get_idx(dependsJ, i));
				if( (sep = sourceStr.find("/")) != std::string::npos)
				{
					AFB_ERROR("Signal composition needs to use signal 'id', don't use full low level signal name");
					return -1;
				}
				dependsV.push_back(sourceStr);
			}
		}
		else
		{
			std::string sourceStr = json_object_get_string(dependsJ);
			if( (sep = sourceStr.find("/")) != std::string::npos)
			{
				AFB_ERROR("Signal composition needs to use signal 'id', don't use full low level signal name");
				return -1;
			}
			dependsV.push_back(sourceStr);
		}
		if(!src) {src=*sourcesListV_.rbegin();}
	}

	// Set default retention if not specified
	if(!retention)
	{
		retention = src->signalsDefault().retention ?
			src->signalsDefault().retention :
			30;
	}
	unit = !unit ? "" : unit;

	// Set default onReceived action if not specified
	char uid[CONTROL_MAXPATH_LEN] = "onReceived_";
	strncat(uid, id, strlen(id));

	if(!onReceivedJ)
	{
		onReceivedCtl = src->signalsDefault().onReceived ?
			src->signalsDefault().onReceived :
			nullptr;
			// Overwrite uid to the signal one instead of the default
			if(onReceivedCtl)
				{onReceivedCtl->uid = uid;}
	}
	else {onReceivedCtl = convert2Action(uid, onReceivedJ);}

	if(src != nullptr)
		{src->addSignal(id, event, dependsV, retention, unit, frequency, onReceivedCtl, getSignalsArgs);}
	else
		{err = -1;}

	return err;
}

int Composer::loadSignals(AFB_ApiT apihandle, CtlSectionT* section, json_object *signalsJ)
{
	int err = 0;
	Composer& composer = instance();

	if(signalsJ)
	{
		if (json_object_get_type(signalsJ) == json_type_array)
		{
			int count = json_object_array_length(signalsJ);

			for (int idx = 0; idx < count; idx++)
			{
				json_object *signalJ = json_object_array_get_idx(signalsJ, idx);
				err += composer.loadOneSignal(signalJ);
			}
		}
		else
			{err = composer.loadOneSignal(signalsJ);}
	}

	return err;
}

void Composer::processOptions(const std::map<std::string, int>& opts, std::shared_ptr<Signal> sig, json_object* response) const
{
	for(const auto& o: opts)
	{
		bool avg = false, min = false, max = false, last = false;
		if (o.first.compare("average") && !avg)
		{
			avg = true;
			double value = sig->average(o.second);
			json_object_object_add(response, "value",
				json_object_new_double(value));
		}
		else if (o.first.compare("minimum") && !min)
		{
			min = true;
			double value = sig->minimum();
			json_object_object_add(response, "value",
				json_object_new_double(value));
		}
		else if (o.first.compare("maximum") && !max)
		{
			max = true;
			double value = sig->maximum();
			json_object_object_add(response, "value",
				json_object_new_double(value));
		}
		else if (o.first.compare("last") && !last)
		{
			last = true;
			struct signalValue value = sig->last();
			if(value.hasBool)
			{
				json_object_object_add(response, "value",
					json_object_new_boolean(value.boolVal));
			}
			else if(value.hasNum)
			{
				json_object_object_add(response, "value",
					json_object_new_double(value.numVal));
			}
			else if(value.hasStr)
			{
				json_object_object_add(response, "value",
					json_object_new_string(value.strVal.c_str()));
			}
			else
			{
				json_object_object_add(response, "value",
					json_object_new_string("No recorded value so far."));
			}
		}
		else
		{
			json_object_object_add(response, "value",
				json_object_new_string("No recorded value so far."));
		}
	}
}

///////////////////////////////////////////////////////////////////////////////
//                             PUBLIC METHODS                                //
///////////////////////////////////////////////////////////////////////////////

Composer& Composer::instance()
{
	static Composer composer;
	return composer;
}

void* Composer::createContext(void* ctx)
{
	uuid_t x;
	char cuid[38];
	uuid_generate(x);
	uuid_unparse(x, cuid);
	clientAppCtx* ret = new clientAppCtx(cuid);

	return (void*)ret;
}

void Composer::destroyContext(void* ctx)
{
	delete(reinterpret_cast<clientAppCtx*>(ctx));
}

std::vector<std::string> Composer::parseURI(const std::string& uri)
{
	std::vector<std::string> uriV;
	std::string delimiters = "/";

	std::string::size_type start = 0;
	auto pos = uri.find_first_of(delimiters, start);
	while(pos != std::string::npos)
	{
		if(pos != start) // ignore empty tokens
			uriV.emplace_back(uri, start, pos - start);
		start = pos + 1;
		pos = uri.find_first_of(delimiters, start);
	}

	if(start < uri.length()) // ignore trailing delimiter
	uriV.emplace_back(uri, start, uri.length() - start); // add what's left of the string

	return uriV;
}

int Composer::loadConfig(const std::string& filepath)
{
	const char *dirList= getenv("CONTROL_CONFIG_PATH");
	if (!dirList) dirList=CONTROL_CONFIG_PATH;
	const char *configPath = CtlConfigSearch (nullptr, dirList, "control-");

	if (!configPath) {
		AFB_ApiError(apiHandle, "CtlPreInit: No control-* config found invalid JSON %s ", dirList);
		return -1;
	}

	// create one API per file
	ctlConfig_ = CtlLoadMetaData(nullptr, configPath);
	if (!ctlConfig_) {
		AFB_ApiError(apiHandle, "CtrlPreInit No valid control config file in:\n-- %s", configPath);
		return -1;
	}

	if (ctlConfig_->api) {
		int err = afb_daemon_rename_api(ctlConfig_->api);
		if (err) {
			AFB_ApiError(apiHandle, "Fail to rename api to:%s", ctlConfig_->api);
			return -1;
		}
	}

	int err= CtlLoadSections(nullptr, ctlConfig_, ctlSections_);
	return err;
}

int Composer::loadSources(json_object* sourcesJ)
{
	int err = loadSourcesAPI(nullptr, nullptr, sourcesJ);
	if(err)
	{
		AFB_ERROR("Loading sources failed. JSON: %s", json_object_to_json_string(sourcesJ));
		return err;
	}
	initSourcesAPI();
	return err;
}

int Composer::loadSignals(json_object* signalsJ)
{
	return loadSignals(nullptr, nullptr, signalsJ);
}

CtlConfigT* Composer::ctlConfig()
{
	return ctlConfig_;
}

void Composer::initSourcesAPI()
{
	for(int i=0; i < newSourcesListV_.size(); i++)
	{
		std::shared_ptr<SourceAPI> src = newSourcesListV_.back();
		newSourcesListV_.pop_back();
		src->init();
		sourcesListV_.push_back(src);
	}
}

void Composer::initSignals()
{
	for(int i=0; i < sourcesListV_.size(); i++)
	{
		std::shared_ptr<SourceAPI> src = sourcesListV_[i];
		src->initSignals();
	}
	execSignalsSubscription();
}

std::shared_ptr<SourceAPI> Composer::getSourceAPI(const std::string& api)
{
	for(auto& source: sourcesListV_)
	{
		if (source->api() == api)
			{return source;}
	}
	for(auto& source: newSourcesListV_)
	{
		if (source->api() == api)
			{return source;}
	}
	return nullptr;
}

std::vector<std::shared_ptr<Signal>> Composer::getAllSignals()
{
	std::vector<std::shared_ptr<Signal>> allSignals;
	for( auto& source : sourcesListV_)
	{
		std::vector<std::shared_ptr<Signal>> srcSignals = source->getSignals();
		allSignals.insert(allSignals.end(), srcSignals.begin(), srcSignals.end());
	}

	return allSignals;
}

std::vector<std::shared_ptr<Signal>> Composer::searchSignals(const std::string& aName)
{
	std::string api;
	std::vector<std::shared_ptr<Signal>> signals;
	size_t sep = aName.find_first_of("/");
	if(sep != std::string::npos)
	{
		api = aName.substr(0, sep);
		std::shared_ptr<SourceAPI> source = getSourceAPI(api);
		return source->searchSignals(aName);
	}
	else
	{
		std::vector<std::shared_ptr<Signal>> allSignals = getAllSignals();
		for (std::shared_ptr<Signal>& sig : allSignals)
		{
			if(*sig == aName)
				{signals.emplace_back(sig);}
		}
	}
	return signals;
}

json_object* Composer::getsignalValue(const std::string& sig, json_object* options)
{
	std::map<std::string, int> opts;
	json_object *response = nullptr, *finalResponse = json_object_new_array();

	wrap_json_unpack(options, "{s?i, s?i, s?i, s?i !}",
		"average", &opts["average"],
		"minimum", &opts["minimum"],
		"maximum", &opts["maximum"],
		"last", &opts["last"]);

	std::vector<std::shared_ptr<Signal>> sigP = searchSignals(sig);
	if(!sigP.empty())
	{
		for(auto& sig: sigP)
		{
			wrap_json_pack(&response, "{ss}",
				"signal", sig->id().c_str());
			if (opts.empty())
			{
				struct signalValue value = sig->last();
				if(value.hasBool)
				{
					json_object_object_add(response, "value",
						json_object_new_boolean(value.boolVal));
				}
				else if(value.hasNum)
				{
					json_object_object_add(response, "value",
						json_object_new_double(value.numVal));
				}
				else if(value.hasStr)
				{
					json_object_object_add(response, "value",
						json_object_new_string(value.strVal.c_str()));
				}
				else
				{
					json_object_object_add(response, "value",
						json_object_new_string("No recorded value so far."));
				}
			}
			else
				{processOptions(opts, sig, response);}
			json_object_array_add(finalResponse, response);
		}
	}

	return finalResponse;
}

void Composer::execSignalsSubscription()
{
	for(std::shared_ptr<SourceAPI> srcAPI: sourcesListV_)
	{
		if (srcAPI->api() != std::string(ctlConfig_->api))
		{
			srcAPI->makeSubscription();
		}
	}
}