[svn-commits] kharwell: trunk r403135 - in /trunk: ./ include/asterisk/ main/ res/ res/ari/...

SVN commits to the Digium repositories svn-commits at lists.digium.com
Sat Nov 23 11:48:31 CST 2013


Author: kharwell
Date: Sat Nov 23 11:48:28 2013
New Revision: 403135

URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=403135
Log:
ARI: Implement device state API

Created a data model and implemented functionality for an ARI device state
resource.  The following operations have been added that allow a user to
manipulate an ARI controlled device:

Create/Change the state of an ARI controlled device
PUT    /deviceStates/{deviceName}&{deviceState}

Retrieve all ARI controlled devices
GET    /deviceStates

Retrieve the current state of a device
GET    /deviceStates/{deviceName}

Destroy a device-state controlled by ARI
DELETE /deviceStates/{deviceName}

The ARI controlled device must begin with 'Stasis:'.  An example controlled
device name would be Stasis:Example.  A 'DeviceStateChanged' event has also
been added so that an application can subscribe and receive device change
events.  Any device state, ARI controlled or not, can be subscribed to.

While adding the event, the underlying subscription control mechanism was
refactored so that all current and future resource subscriptions would be
the same.  Each event resource must now register itself in order to be able
to properly handle [un]subscribes.

(issue ASTERISK-22838)
Reported by: Matt Jordan
Review: https://reviewboard.asterisk.org/r/3025/
........

Merged revisions 403134 from http://svn.asterisk.org/svn/asterisk/branches/12

Added:
    trunk/include/asterisk/stasis_app_device_state.h
      - copied unchanged from r403134, branches/12/include/asterisk/stasis_app_device_state.h
    trunk/res/ari/resource_device_states.c
      - copied unchanged from r403134, branches/12/res/ari/resource_device_states.c
    trunk/res/ari/resource_device_states.h
      - copied unchanged from r403134, branches/12/res/ari/resource_device_states.h
    trunk/res/res_ari_device_states.c
      - copied unchanged from r403134, branches/12/res/res_ari_device_states.c
    trunk/res/res_stasis_device_state.c
      - copied unchanged from r403134, branches/12/res/res_stasis_device_state.c
    trunk/res/res_stasis_device_state.exports.in
      - copied unchanged from r403134, branches/12/res/res_stasis_device_state.exports.in
    trunk/rest-api/api-docs/deviceStates.json
      - copied unchanged from r403134, branches/12/rest-api/api-docs/deviceStates.json
Modified:
    trunk/   (props changed)
    trunk/include/asterisk/devicestate.h
    trunk/include/asterisk/stasis_app.h
    trunk/main/devicestate.c
    trunk/res/ari.make
    trunk/res/ari/ari_model_validators.c
    trunk/res/ari/ari_model_validators.h
    trunk/res/ari/resource_applications.h
    trunk/res/res_stasis.c
    trunk/res/stasis/app.c
    trunk/res/stasis/app.h
    trunk/rest-api-templates/ari.make.mustache
    trunk/rest-api/api-docs/applications.json
    trunk/rest-api/api-docs/events.json
    trunk/rest-api/resources.json

Propchange: trunk/
------------------------------------------------------------------------------
--- branch-12-merged (original)
+++ branch-12-merged Sat Nov 23 11:48:28 2013
@@ -1,1 +1,1 @@
-/branches/12:1-398558,398560-398577,398579-399305,399307-401390,401392-402993,403016,403022,403069,403082,403094,403117,403119,403131
+/branches/12:1-398558,398560-398577,398579-399305,399307-401390,401392-402993,403016,403022,403069,403082,403094,403117,403119,403131,403134

Modified: trunk/include/asterisk/devicestate.h
URL: http://svnview.digium.com/svn/asterisk/trunk/include/asterisk/devicestate.h?view=diff&rev=403135&r1=403134&r2=403135
==============================================================================
--- trunk/include/asterisk/devicestate.h (original)
+++ trunk/include/asterisk/devicestate.h Sat Nov 23 11:48:28 2013
@@ -325,6 +325,15 @@
 struct stasis_message_type *ast_device_state_message_type(void);
 
 /*!
+ * \brief Clear the device from the stasis cache.
+ * \param The device to clear
+ * \retval 0 if successful
+ * \retval -1 nothing to clear
+ * \since 12
+ */
+int ast_device_state_clear_cache(const char *device);
+
+/*!
  * \brief Initialize the device state core
  * \retval 0 Success
  * \retval -1 Failure

Modified: trunk/include/asterisk/stasis_app.h
URL: http://svnview.digium.com/svn/asterisk/trunk/include/asterisk/stasis_app.h?view=diff&rev=403135&r1=403134&r2=403135
==============================================================================
--- trunk/include/asterisk/stasis_app.h (original)
+++ trunk/include/asterisk/stasis_app.h Sat Nov 23 11:48:28 2013
@@ -111,6 +111,18 @@
  */
 int stasis_app_send(const char *app_name, struct ast_json *message);
 
+/*! \brief Forward declare app */
+struct stasis_app;
+
+/*!
+ * \brief Retrieve an application's name
+ *
+ * \param app An application
+ *
+ * \return The name of the application.
+ */
+const char *stasis_app_name(const struct stasis_app *app);
+
 /*!
  * \brief Return the JSON representation of a Stasis application.
  *
@@ -120,6 +132,102 @@
  * \return \c NULL on error.
  */
 struct ast_json *stasis_app_to_json(const char *app_name);
+
+/*!
+ * \brief Event source information and callbacks.
+ */
+struct stasis_app_event_source {
+	/*! \brief The scheme to match against on [un]subscribes */
+	const char *scheme;
+
+	/*!
+	 * \brief Find an event source data object by the given id/name.
+	 *
+	 * \param app Application
+	 * \param id A unique identifier to search on
+	 *
+	 * \return The data object associated with the id/name.
+	 */
+	void *(*find)(const struct stasis_app *app, const char *id);
+
+	/*!
+	 * \brief Subscribe an application to an event source.
+	 *
+	 * \param app Application
+	 * \param obj an event source data object
+	 *
+	 * \return 0 on success, failure code otherwise
+	 */
+	int (*subscribe)(struct stasis_app *app, void *obj);
+
+	/*!
+	 * \brief Cancel the subscription an app has to an event source.
+	 *
+	 * \param app Application
+	 * \param id a previously subscribed object id
+	 *
+	 * \return 0 on success, failure code otherwise
+	 */
+	int (*unsubscribe)(struct stasis_app *app, const char *id);
+
+	/*!
+	 * \brief Find an event source by the given id/name.
+	 *
+	 * \param app Application
+	 * \param id A unique identifier to check
+	 *
+	 * \return true if id is subscribed, false otherwise.
+	 */
+	int (*is_subscribed)(struct stasis_app *app, const char *id);
+
+	/*!
+	 * \brief Convert event source data to json
+	 *
+	 * \param app Application
+	 * \param id json object to fill
+	 */
+	void (*to_json)(const struct stasis_app *app, struct ast_json *json);
+
+	/*! Next item in the list */
+	AST_LIST_ENTRY(stasis_app_event_source) next;
+};
+
+/*!
+ * \brief Register an application event source.
+ *
+ * \param obj the event source to register
+ */
+void stasis_app_register_event_source(struct stasis_app_event_source *obj);
+
+/*!
+ * \brief Register core event sources.
+ */
+void stasis_app_register_event_sources(void);
+
+/*!
+ * \brief Checks to see if the given object is a core event source
+ *
+ * \note core event sources are currently only endpoint, bridge, and channel.
+ *
+ * \param obj event source object to check
+ *
+ * \return non-zero if core event source, otherwise 0 (false)
+
+ */
+int stasis_app_is_core_event_source(struct stasis_app_event_source *obj);
+
+/*!
+ * \brief Unregister an application event source.
+ *
+ * \param obj the event source to unregister
+ */
+void stasis_app_unregister_event_source(struct stasis_app_event_source *obj);
+
+/*!
+ * \brief Unregister core event sources.
+ */
+void stasis_app_unregister_event_sources(void);
+
 
 /*! \brief Return code for stasis_app_[un]subscribe */
 enum stasis_app_subscribe_res {

Modified: trunk/main/devicestate.c
URL: http://svnview.digium.com/svn/asterisk/trunk/main/devicestate.c?view=diff&rev=403135&r1=403134&r2=403135
==============================================================================
--- trunk/main/devicestate.c (original)
+++ trunk/main/devicestate.c Sat Nov 23 11:48:28 2013
@@ -734,6 +734,22 @@
 	return stasis_topic_pool_get_topic(device_state_topic_pool, device);
 }
 
+int ast_device_state_clear_cache(const char *device)
+{
+	RAII_VAR(struct stasis_message *, cached_msg, NULL, ao2_cleanup);
+	RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
+
+	if (!(cached_msg = stasis_cache_get(ast_device_state_cache(),
+					    ast_device_state_message_type(), device))) {
+		/* nothing to clear */
+		return -1;
+	}
+
+	msg = stasis_cache_clear_create(cached_msg);
+	stasis_publish(ast_device_state_topic(device), msg);
+	return 0;
+}
+
 int ast_publish_device_state_full(
 			const char *device,
 			enum ast_device_state state,

Modified: trunk/res/ari.make
URL: http://svnview.digium.com/svn/asterisk/trunk/res/ari.make?view=diff&rev=403135&r1=403134&r2=403135
==============================================================================
--- trunk/res/ari.make (original)
+++ trunk/res/ari.make Sat Nov 23 11:48:28 2013
@@ -45,6 +45,10 @@
 
 ari/resource_playbacks.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_ari_playbacks)
 
+res_ari_device_states.so: ari/resource_device_states.o
+
+ari/resource_device_states.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_ari_device_states)
+
 res_ari_events.so: ari/resource_events.o
 
 ari/resource_events.o: _ASTCFLAGS+=$(call MOD_ASTCFLAGS,res_ari_events)

Modified: trunk/res/ari/ari_model_validators.c
URL: http://svnview.digium.com/svn/asterisk/trunk/res/ari/ari_model_validators.c?view=diff&rev=403135&r1=403134&r2=403135
==============================================================================
--- trunk/res/ari/ari_model_validators.c (original)
+++ trunk/res/ari/ari_model_validators.c Sat Nov 23 11:48:28 2013
@@ -1333,6 +1333,60 @@
 	return ast_ari_validate_playback;
 }
 
+int ast_ari_validate_device_state(struct ast_json *json)
+{
+	int res = 1;
+	struct ast_json_iter *iter;
+	int has_name = 0;
+	int has_state = 0;
+
+	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+		if (strcmp("name", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_name = 1;
+			prop_is_valid = ast_ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI DeviceState field name failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("state", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_state = 1;
+			prop_is_valid = ast_ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI DeviceState field state failed validation\n");
+				res = 0;
+			}
+		} else
+		{
+			ast_log(LOG_ERROR,
+				"ARI DeviceState has undocumented field %s\n",
+				ast_json_object_iter_key(iter));
+			res = 0;
+		}
+	}
+
+	if (!has_name) {
+		ast_log(LOG_ERROR, "ARI DeviceState missing required field name\n");
+		res = 0;
+	}
+
+	if (!has_state) {
+		ast_log(LOG_ERROR, "ARI DeviceState missing required field state\n");
+		res = 0;
+	}
+
+	return res;
+}
+
+ari_validator ast_ari_validate_device_state_fn(void)
+{
+	return ast_ari_validate_device_state;
+}
+
 int ast_ari_validate_application_replaced(struct ast_json *json)
 {
 	int res = 1;
@@ -2746,6 +2800,85 @@
 	return ast_ari_validate_channel_varset;
 }
 
+int ast_ari_validate_device_state_changed(struct ast_json *json)
+{
+	int res = 1;
+	struct ast_json_iter *iter;
+	int has_type = 0;
+	int has_application = 0;
+	int has_device_state = 0;
+
+	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+		if (strcmp("type", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_type = 1;
+			prop_is_valid = ast_ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI DeviceStateChanged field type failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("application", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_application = 1;
+			prop_is_valid = ast_ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI DeviceStateChanged field application failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("timestamp", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			prop_is_valid = ast_ari_validate_date(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI DeviceStateChanged field timestamp failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("device_state", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_device_state = 1;
+			prop_is_valid = ast_ari_validate_device_state(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI DeviceStateChanged field device_state failed validation\n");
+				res = 0;
+			}
+		} else
+		{
+			ast_log(LOG_ERROR,
+				"ARI DeviceStateChanged has undocumented field %s\n",
+				ast_json_object_iter_key(iter));
+			res = 0;
+		}
+	}
+
+	if (!has_type) {
+		ast_log(LOG_ERROR, "ARI DeviceStateChanged missing required field type\n");
+		res = 0;
+	}
+
+	if (!has_application) {
+		ast_log(LOG_ERROR, "ARI DeviceStateChanged missing required field application\n");
+		res = 0;
+	}
+
+	if (!has_device_state) {
+		ast_log(LOG_ERROR, "ARI DeviceStateChanged missing required field device_state\n");
+		res = 0;
+	}
+
+	return res;
+}
+
+ari_validator ast_ari_validate_device_state_changed_fn(void)
+{
+	return ast_ari_validate_device_state_changed;
+}
+
 int ast_ari_validate_endpoint_state_change(struct ast_json *json)
 {
 	int res = 1;
@@ -2887,6 +3020,9 @@
 	if (strcmp("ChannelVarset", discriminator) == 0) {
 		return ast_ari_validate_channel_varset(json);
 	} else
+	if (strcmp("DeviceStateChanged", discriminator) == 0) {
+		return ast_ari_validate_device_state_changed(json);
+	} else
 	if (strcmp("EndpointStateChange", discriminator) == 0) {
 		return ast_ari_validate_endpoint_state_change(json);
 	} else
@@ -3025,6 +3161,9 @@
 	if (strcmp("ChannelVarset", discriminator) == 0) {
 		return ast_ari_validate_channel_varset(json);
 	} else
+	if (strcmp("DeviceStateChanged", discriminator) == 0) {
+		return ast_ari_validate_device_state_changed(json);
+	} else
 	if (strcmp("EndpointStateChange", discriminator) == 0) {
 		return ast_ari_validate_endpoint_state_change(json);
 	} else
@@ -3592,6 +3731,7 @@
 	struct ast_json_iter *iter;
 	int has_bridge_ids = 0;
 	int has_channel_ids = 0;
+	int has_device_names = 0;
 	int has_endpoint_ids = 0;
 	int has_name = 0;
 
@@ -3618,6 +3758,17 @@
 				res = 0;
 			}
 		} else
+		if (strcmp("device_names", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_device_names = 1;
+			prop_is_valid = ast_ari_validate_list(
+				ast_json_object_iter_value(iter),
+				ast_ari_validate_string);
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI Application field device_names failed validation\n");
+				res = 0;
+			}
+		} else
 		if (strcmp("endpoint_ids", ast_json_object_iter_key(iter)) == 0) {
 			int prop_is_valid;
 			has_endpoint_ids = 1;
@@ -3657,6 +3808,11 @@
 		res = 0;
 	}
 
+	if (!has_device_names) {
+		ast_log(LOG_ERROR, "ARI Application missing required field device_names\n");
+		res = 0;
+	}
+
 	if (!has_endpoint_ids) {
 		ast_log(LOG_ERROR, "ARI Application missing required field endpoint_ids\n");
 		res = 0;

Modified: trunk/res/ari/ari_model_validators.h
URL: http://svnview.digium.com/svn/asterisk/trunk/res/ari/ari_model_validators.h?view=diff&rev=403135&r1=403134&r2=403135
==============================================================================
--- trunk/res/ari/ari_model_validators.h (original)
+++ trunk/res/ari/ari_model_validators.h Sat Nov 23 11:48:28 2013
@@ -481,6 +481,24 @@
 ari_validator ast_ari_validate_playback_fn(void);
 
 /*!
+ * \brief Validator for DeviceState.
+ *
+ * Represents the state of a device.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ast_ari_validate_device_state(struct ast_json *json);
+
+/*!
+ * \brief Function pointer to ast_ari_validate_device_state().
+ *
+ * See \ref ast_ari_model_validators.h for more details.
+ */
+ari_validator ast_ari_validate_device_state_fn(void);
+
+/*!
  * \brief Validator for ApplicationReplaced.
  *
  * Notification that another WebSocket has taken over for an application.
@@ -753,6 +771,24 @@
  * See \ref ast_ari_model_validators.h for more details.
  */
 ari_validator ast_ari_validate_channel_varset_fn(void);
+
+/*!
+ * \brief Validator for DeviceStateChanged.
+ *
+ * Notification that a device state has changed.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ast_ari_validate_device_state_changed(struct ast_json *json);
+
+/*!
+ * \brief Function pointer to ast_ari_validate_device_state_changed().
+ *
+ * See \ref ast_ari_model_validators.h for more details.
+ */
+ari_validator ast_ari_validate_device_state_changed_fn(void);
 
 /*!
  * \brief Validator for EndpointStateChange.
@@ -1052,6 +1088,9 @@
  * - media_uri: string (required)
  * - state: string (required)
  * - target_uri: string (required)
+ * DeviceState
+ * - name: string (required)
+ * - state: string (required)
  * ApplicationReplaced
  * - type: string (required)
  * - application: string (required)
@@ -1143,6 +1182,11 @@
  * - channel: Channel
  * - value: string (required)
  * - variable: string (required)
+ * DeviceStateChanged
+ * - type: string (required)
+ * - application: string (required)
+ * - timestamp: Date
+ * - device_state: DeviceState (required)
  * EndpointStateChange
  * - type: string (required)
  * - application: string (required)
@@ -1187,6 +1231,7 @@
  * Application
  * - bridge_ids: List[string] (required)
  * - channel_ids: List[string] (required)
+ * - device_names: List[string] (required)
  * - endpoint_ids: List[string] (required)
  * - name: string (required)
  */

Modified: trunk/res/ari/resource_applications.h
URL: http://svnview.digium.com/svn/asterisk/trunk/res/ari/resource_applications.h?view=diff&rev=403135&r1=403134&r2=403135
==============================================================================
--- trunk/res/ari/resource_applications.h (original)
+++ trunk/res/ari/resource_applications.h Sat Nov 23 11:48:28 2013
@@ -67,7 +67,7 @@
 struct ast_ari_applications_subscribe_args {
 	/*! \brief Application's name */
 	const char *application_name;
-	/*! \brief Array of URI for event source (channel:{channelId}, bridge:{bridgeId}, endpoint:{tech}/{resource} */
+	/*! \brief Array of URI for event source (channel:{channelId}, bridge:{bridgeId}, endpoint:{tech}/{resource}, deviceState:{deviceName} */
 	const char **event_source;
 	/*! \brief Length of event_source array. */
 	size_t event_source_count;
@@ -88,7 +88,7 @@
 struct ast_ari_applications_unsubscribe_args {
 	/*! \brief Application's name */
 	const char *application_name;
-	/*! \brief Array of URI for event source (channel:{channelId}, bridge:{bridgeId}, endpoint:{tech}/{resource} */
+	/*! \brief Array of URI for event source (channel:{channelId}, bridge:{bridgeId}, endpoint:{tech}/{resource}, device_state:{deviceName} */
 	const char **event_source;
 	/*! \brief Length of event_source array. */
 	size_t event_source_count;

Modified: trunk/res/res_stasis.c
URL: http://svnview.digium.com/svn/asterisk/trunk/res/res_stasis.c?view=diff&rev=403135&r1=403134&r2=403135
==============================================================================
--- trunk/res/res_stasis.c (original)
+++ trunk/res/res_stasis.c Sat Nov 23 11:48:28 2013
@@ -103,10 +103,15 @@
 
 struct ao2_container *app_bridges_moh;
 
+const char *stasis_app_name(const struct stasis_app *app)
+{
+	return app_name(app);
+}
+
 /*! AO2 hash function for \ref app */
 static int app_hash(const void *obj, const int flags)
 {
-	const struct app *app;
+	const struct stasis_app *app;
 	const char *key;
 
 	switch (flags & OBJ_SEARCH_MASK) {
@@ -115,7 +120,7 @@
 		break;
 	case OBJ_SEARCH_OBJECT:
 		app = obj;
-		key = app_name(app);
+		key = stasis_app_name(app);
 		break;
 	default:
 		/* Hash can only work on something with a full key. */
@@ -128,24 +133,24 @@
 /*! AO2 comparison function for \ref app */
 static int app_compare(void *obj, void *arg, int flags)
 {
-	const struct app *object_left = obj;
-	const struct app *object_right = arg;
+	const struct stasis_app *object_left = obj;
+	const struct stasis_app *object_right = arg;
 	const char *right_key = arg;
 	int cmp;
 
 	switch (flags & OBJ_SEARCH_MASK) {
 	case OBJ_SEARCH_OBJECT:
-		right_key = app_name(object_right);
+		right_key = stasis_app_name(object_right);
 		/* Fall through */
 	case OBJ_SEARCH_KEY:
-		cmp = strcmp(app_name(object_left), right_key);
+		cmp = strcmp(stasis_app_name(object_left), right_key);
 		break;
 	case OBJ_SEARCH_PARTIAL_KEY:
 		/*
 		 * We could also use a partial key struct containing a length
 		 * so strlen() does not get called for every comparison instead.
 		 */
-		cmp = strncmp(app_name(object_left), right_key, strlen(right_key));
+		cmp = strncmp(stasis_app_name(object_left), right_key, strlen(right_key));
 		break;
 	default:
 		/*
@@ -229,13 +234,13 @@
 
 static int cleanup_cb(void *obj, void *arg, int flags)
 {
-	struct app *app = obj;
+	struct stasis_app *app = obj;
 
 	if (!app_is_finished(app)) {
 		return 0;
 	}
 
-	ast_verb(1, "Shutting down application '%s'\n", app_name(app));
+	ast_verb(1, "Shutting down application '%s'\n", stasis_app_name(app));
 	app_shutdown(app);
 
 	return CMP_MATCH;
@@ -619,7 +624,7 @@
 	ast_bridge_destroy(bridge, 0);
 }
 
-static int send_start_msg(struct app *app, struct ast_channel *chan,
+static int send_start_msg(struct stasis_app *app, struct ast_channel *chan,
 	int argc, char *argv[])
 {
 	RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref);
@@ -667,7 +672,7 @@
 	return 0;
 }
 
-static int send_end_msg(struct app *app, struct ast_channel *chan)
+static int send_end_msg(struct stasis_app *app, struct ast_channel *chan)
 {
 	RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref);
 	RAII_VAR(struct ast_channel_snapshot *, snapshot, NULL, ao2_cleanup);
@@ -714,7 +719,7 @@
 {
 	SCOPED_MODULE_USE(ast_module_info->self);
 
-	RAII_VAR(struct app *, app, NULL, ao2_cleanup);
+	RAII_VAR(struct stasis_app *, app, NULL, ao2_cleanup);
 	RAII_VAR(struct stasis_app_control *, control, NULL, control_unlink);
 	struct ast_bridge *last_bridge = NULL;
 	int res = 0;
@@ -838,7 +843,7 @@
 
 int stasis_app_send(const char *app_name, struct ast_json *message)
 {
-	RAII_VAR(struct app *, app, NULL, ao2_cleanup);
+	RAII_VAR(struct stasis_app *, app, NULL, ao2_cleanup);
 
 	app = ao2_find(apps_registry, app_name, OBJ_SEARCH_KEY);
 	if (!app) {
@@ -849,17 +854,31 @@
 			"Stasis app '%s' not registered\n", app_name);
 		return -1;
 	}
-
 	app_send(app, message);
 	return 0;
 }
 
+static struct stasis_app *find_app_by_name(const char *app_name)
+{
+	struct stasis_app *res = NULL;
+
+	if (!ast_strlen_zero(app_name)) {
+		res = ao2_find(apps_registry, app_name, OBJ_SEARCH_KEY);
+	}
+
+	if (!res) {
+		ast_log(LOG_WARNING, "Could not find app '%s'\n",
+			app_name ? : "(null)");
+	}
+	return res;
+}
+
 static int append_name(void *obj, void *arg, int flags)
 {
-	struct app *app = obj;
+	struct stasis_app *app = obj;
 	struct ao2_container *apps = arg;
 
-	ast_str_container_add(apps, app_name(app));
+	ast_str_container_add(apps, stasis_app_name(app));
 	return 0;
 }
 
@@ -879,7 +898,7 @@
 
 int stasis_app_register(const char *app_name, stasis_app_cb handler, void *data)
 {
-	RAII_VAR(struct app *, app, NULL, ao2_cleanup);
+	RAII_VAR(struct stasis_app *, app, NULL, ao2_cleanup);
 
 	SCOPED_LOCK(apps_lock, apps_registry, ao2_lock, ao2_unlock);
 
@@ -904,7 +923,7 @@
 
 void stasis_app_unregister(const char *app_name)
 {
-	RAII_VAR(struct app *, app, NULL, ao2_cleanup);
+	RAII_VAR(struct stasis_app *, app, NULL, ao2_cleanup);
 
 	if (!app_name) {
 		return;
@@ -925,217 +944,249 @@
 	cleanup();
 }
 
-struct ast_json *stasis_app_to_json(const char *app_name)
-{
-	RAII_VAR(struct app *, app, NULL, ao2_cleanup);
-
-	if (app_name) {
-		app = ao2_find(apps_registry, app_name, OBJ_SEARCH_KEY);
-	}
-
+/*!
+ * \internal \brief List of registered event sources.
+ */
+AST_RWLIST_HEAD_STATIC(event_sources, stasis_app_event_source);
+
+void stasis_app_register_event_source(struct stasis_app_event_source *obj)
+{
+	SCOPED_LOCK(lock, &event_sources, AST_RWLIST_WRLOCK, AST_RWLIST_UNLOCK);
+	AST_LIST_INSERT_TAIL(&event_sources, obj, next);
+	/* only need to bump the module ref on non-core sources because the
+	   core ones are [un]registered by this module. */
+	if (!stasis_app_is_core_event_source(obj)) {
+		ast_module_ref(ast_module_info->self);
+	}
+}
+
+void stasis_app_unregister_event_source(struct stasis_app_event_source *obj)
+{
+	struct stasis_app_event_source *source;
+	SCOPED_LOCK(lock, &event_sources, AST_RWLIST_WRLOCK, AST_RWLIST_UNLOCK);
+	AST_RWLIST_TRAVERSE_SAFE_BEGIN(&event_sources, source, next) {
+		if (source == obj) {
+			AST_RWLIST_REMOVE_CURRENT(next);
+			if (!stasis_app_is_core_event_source(obj)) {
+				ast_module_unref(ast_module_info->self);
+			}
+			break;
+		}
+	}
+	AST_RWLIST_TRAVERSE_SAFE_END;
+}
+
+/*!
+ * \internal
+ * \brief Convert event source data to JSON.
+ *
+ * Calls each event source that has a "to_json" handler allowing each
+ * source to add data to the given JSON object.
+ *
+ * \param app application associated with the event source
+ * \param json a json object to "fill"
+ *
+ * \retval The given json object.
+ */
+static struct ast_json *app_event_sources_to_json(
+	const struct stasis_app *app, struct ast_json *json)
+{
+	struct stasis_app_event_source *source;
+	SCOPED_LOCK(lock, &event_sources, AST_RWLIST_RDLOCK, AST_RWLIST_UNLOCK);
+	AST_LIST_TRAVERSE(&event_sources, source, next) {
+		if (source->to_json) {
+			source->to_json(app, json);
+		}
+	}
+	return json;
+}
+
+static struct ast_json *stasis_app_object_to_json(struct stasis_app *app)
+{
 	if (!app) {
 		return NULL;
 	}
 
-	return app_to_json(app);
-}
-
-#define CHANNEL_SCHEME "channel:"
-#define BRIDGE_SCHEME "bridge:"
-#define ENDPOINT_SCHEME "endpoint:"
-
-/*! Struct for capturing event source information */
-struct event_source {
-	enum {
-		EVENT_SOURCE_CHANNEL,
-		EVENT_SOURCE_BRIDGE,
-		EVENT_SOURCE_ENDPOINT,
-	} event_source_type;
-	union {
-		struct ast_channel *channel;
-		struct ast_bridge *bridge;
-		struct ast_endpoint *endpoint;
-	};
-};
+	return app_event_sources_to_json(app, app_to_json(app));
+}
+
+struct ast_json *stasis_app_to_json(const char *app_name)
+{
+	RAII_VAR(struct stasis_app *, app, find_app_by_name(app_name), ao2_cleanup);
+
+	return stasis_app_object_to_json(app);
+}
+
+/*!
+ * \internal
+ * \brief Finds an event source that matches a uri scheme.
+ *
+ * Uri(s) should begin with a particular scheme that can be matched
+ * against an event source.
+ *
+ * \param uri uri containing a scheme to match
+ *
+ * \retval an event source if found, NULL otherwise.
+ */
+static struct stasis_app_event_source *app_event_source_find(const char *uri)
+{
+	struct stasis_app_event_source *source;
+	SCOPED_LOCK(lock, &event_sources, AST_RWLIST_RDLOCK, AST_RWLIST_UNLOCK);
+	AST_LIST_TRAVERSE(&event_sources, source, next) {
+		if (ast_begins_with(uri, source->scheme)) {
+			return source;
+		}
+	}
+	return NULL;
+}
+
+/*!
+ * \internal
+ * \brief Callback for subscription handling
+ *
+ * \param app [un]subscribing application
+ * \param uri scheme:id of an event source
+ * \param event_source being [un]subscribed [from]to
+ *
+ * \retval stasis_app_subscribe_res return code.
+ */
+typedef enum stasis_app_subscribe_res (*app_subscription_handler)(
+	struct stasis_app *app, const char *uri,
+	struct stasis_app_event_source *event_source);
+
+/*!
+ * \internal
+ * \brief Subscriptions handler for application [un]subscribing.
+ *
+ * \param app_name Name of the application to subscribe.
+ * \param event_source_uris URIs for the event sources to subscribe to.
+ * \param event_sources_count Array size of event_source_uris.
+ * \param json Optional output pointer for JSON representation of the app
+ *             after adding the subscription.
+ * \param handler [un]subscribe handler
+ *
+ * \retval stasis_app_subscribe_res return code.
+ */
+static enum stasis_app_subscribe_res app_handle_subscriptions(
+	const char *app_name, const char **event_source_uris,
+	int event_sources_count, struct ast_json **json,
+	app_subscription_handler handler)
+{
+	RAII_VAR(struct stasis_app *, app, find_app_by_name(app_name), ao2_cleanup);
+	int i;
+
+	if (!app) {
+		return STASIS_ASR_APP_NOT_FOUND;
+	}
+
+	for (i = 0; i < event_sources_count; ++i) {
+		const char *uri = event_source_uris[i];
+		enum stasis_app_subscribe_res res = STASIS_ASR_INTERNAL_ERROR;
+		struct stasis_app_event_source *event_source;
+
+		if (!(event_source = app_event_source_find(uri))) {
+			ast_log(LOG_WARNING, "Invalid scheme: %s\n", uri);
+			return STASIS_ASR_EVENT_SOURCE_BAD_SCHEME;
+		}
+
+		if (handler &&
+		    ((res = handler(app, uri, event_source)))) {
+			return res;
+		}
+	}
+
+	if (json) {
+		ast_debug(3, "%s: Successful; setting results\n", app_name);
+		*json = stasis_app_object_to_json(app);
+	}
+
+	return STASIS_ASR_OK;
+}
+
+/*!
+ * \internal
+ * \brief Subscribe an app to an event source.
+ *
+ * \param app subscribing application
+ * \param uri scheme:id of an event source
+ * \param event_source being subscribed to
+ *
+ * \retval stasis_app_subscribe_res return code.
+ */
+static enum stasis_app_subscribe_res app_subscribe(
+	struct stasis_app *app, const char *uri,
+	struct stasis_app_event_source *event_source)
+{
+	const char *app_name = stasis_app_name(app);
+	RAII_VAR(void *, obj, NULL, ao2_cleanup);
+
+	ast_debug(3, "%s: Checking %s\n", app_name, uri);
+
+	if (!event_source->find ||
+	    (!(obj = event_source->find(app, uri + strlen(event_source->scheme))))) {
+		ast_log(LOG_WARNING, "Event source not found: %s\n", uri);
+		return STASIS_ASR_EVENT_SOURCE_NOT_FOUND;
+	}
+
+	ast_debug(3, "%s: Subscribing to %s\n", app_name, uri);
+
+	if (!event_source->subscribe || (event_source->subscribe(app, obj))) {
+		ast_log(LOG_WARNING, "Error subscribing app '%s' to '%s'\n",
+			app_name, uri);
+		return STASIS_ASR_INTERNAL_ERROR;
+	}
+
+	return STASIS_ASR_OK;
+}
 
 enum stasis_app_subscribe_res stasis_app_subscribe(const char *app_name,
 	const char **event_source_uris, int event_sources_count,
 	struct ast_json **json)
 {
-	RAII_VAR(struct app *, app, NULL, ao2_cleanup);
-	RAII_VAR(struct event_source *, event_sources, NULL, ast_free);
-	enum stasis_app_subscribe_res res = STASIS_ASR_OK;
-	int i;
-
-	if (app_name) {
-		app = ao2_find(apps_registry, app_name, OBJ_SEARCH_KEY);
-	}
-
-	if (!app) {
-		ast_log(LOG_WARNING, "Could not find app '%s'\n",
-			app_name ? : "(null)");
-		return STASIS_ASR_APP_NOT_FOUND;
-	}
-
-	event_sources = ast_calloc(event_sources_count, sizeof(*event_sources));
-	if (!event_sources) {
-		return STASIS_ASR_INTERNAL_ERROR;
-	}
-
-	for (i = 0; res == STASIS_ASR_OK && i < event_sources_count; ++i) {
-		const char *uri = event_source_uris[i];
-		ast_debug(3, "%s: Checking %s\n", app_name,
-			uri);
-		if (ast_begins_with(uri, CHANNEL_SCHEME)) {
-			event_sources[i].event_source_type =
-				EVENT_SOURCE_CHANNEL;
-			event_sources[i].channel = ast_channel_get_by_name(
-				uri + strlen(CHANNEL_SCHEME));
-			if (!event_sources[i].channel) {
-				ast_log(LOG_WARNING, "Channel not found: %s\n", uri);
-				res = STASIS_ASR_EVENT_SOURCE_NOT_FOUND;
-			}
-		} else if (ast_begins_with(uri, BRIDGE_SCHEME)) {
-			event_sources[i].event_source_type =
-				EVENT_SOURCE_BRIDGE;
-			event_sources[i].bridge = stasis_app_bridge_find_by_id(
-				uri + strlen(BRIDGE_SCHEME));
-			if (!event_sources[i].bridge) {
-				ast_log(LOG_WARNING, "Bridge not found: %s\n", uri);
-				res = STASIS_ASR_EVENT_SOURCE_NOT_FOUND;
-			}
-		} else if (ast_begins_with(uri, ENDPOINT_SCHEME)) {
-			event_sources[i].event_source_type =
-				EVENT_SOURCE_ENDPOINT;
-			event_sources[i].endpoint = ast_endpoint_find_by_id(
-				uri + strlen(ENDPOINT_SCHEME));
-			if (!event_sources[i].endpoint) {
-				ast_log(LOG_WARNING, "Endpoint not found: %s\n", uri);
-				res = STASIS_ASR_EVENT_SOURCE_NOT_FOUND;
-			}
-		} else {
-			ast_log(LOG_WARNING, "Invalid scheme: %s\n", uri);
-			res = STASIS_ASR_EVENT_SOURCE_BAD_SCHEME;
-		}
-	}
-
-	for (i = 0; res == STASIS_ASR_OK && i < event_sources_count; ++i) {
-		int sub_res = -1;
-		ast_debug(1, "%s: Subscribing to %s\n", app_name,
-			event_source_uris[i]);
-
-		switch (event_sources[i].event_source_type) {
-		case EVENT_SOURCE_CHANNEL:
-			sub_res = app_subscribe_channel(app,
-				event_sources[i].channel);
-			break;
-		case EVENT_SOURCE_BRIDGE:
-			sub_res = app_subscribe_bridge(app,
-				event_sources[i].bridge);
-			break;
-		case EVENT_SOURCE_ENDPOINT:
-			sub_res = app_subscribe_endpoint(app,
-				event_sources[i].endpoint);
-			break;
-		}
-
-		if (sub_res != 0) {
-			ast_log(LOG_WARNING,
-				"Error subscribing app '%s' to '%s'\n",
-				app_name, event_source_uris[i]);
-			res = STASIS_ASR_INTERNAL_ERROR;
-		}
-	}
-
-	if (res == STASIS_ASR_OK && json) {
-		ast_debug(1, "%s: Successful; setting results\n", app_name);
-		*json = app_to_json(app);
-	}
-
-	for (i = 0; i < event_sources_count; ++i) {
-		switch (event_sources[i].event_source_type) {
-		case EVENT_SOURCE_CHANNEL:
-			event_sources[i].channel =
-				ast_channel_cleanup(event_sources[i].channel);
-			break;
-		case EVENT_SOURCE_BRIDGE:
-			ao2_cleanup(event_sources[i].bridge);
-			event_sources[i].bridge = NULL;
-			break;
-		case EVENT_SOURCE_ENDPOINT:
-			ao2_cleanup(event_sources[i].endpoint);
-			event_sources[i].endpoint = NULL;
-			break;
-		}
-	}
-
-	return res;
+	return app_handle_subscriptions(
+		app_name, event_source_uris, event_sources_count,
+		json, app_subscribe);
+}
+
+/*!
+ * \internal
+ * \brief Unsubscribe an app from an event source.
+ *
+ * \param app application to unsubscribe
+ * \param uri scheme:id of an event source
+ * \param event_source being unsubscribed from
+ *
+ * \retval stasis_app_subscribe_res return code.
+ */
+static enum stasis_app_subscribe_res app_unsubscribe(
+	struct stasis_app *app, const char *uri,
+	struct stasis_app_event_source *event_source)
+{
+	const char *app_name = stasis_app_name(app);
+	const char *id = uri + strlen(event_source->scheme);
+
+	if (!event_source->is_subscribed ||
+	    (!event_source->is_subscribed(app, id))) {
+		return STASIS_ASR_EVENT_SOURCE_NOT_FOUND;
+	}
+
+	ast_debug(3, "%s: Unsubscribing from %s\n", app_name, uri);
+
+	if (!event_source->unsubscribe || (event_source->unsubscribe(app, id))) {
+		ast_log(LOG_WARNING, "Error unsubscribing app '%s' to '%s'\n",
+			app_name, uri);
+		return -1;
+	}
+	return 0;
 }
 
 enum stasis_app_subscribe_res stasis_app_unsubscribe(const char *app_name,
 	const char **event_source_uris, int event_sources_count,
 	struct ast_json **json)
 {
-	RAII_VAR(struct app *, app, NULL, ao2_cleanup);
-	enum stasis_app_subscribe_res res = STASIS_ASR_OK;
-	int i;
-
-	if (app_name) {
-		app = ao2_find(apps_registry, app_name, OBJ_SEARCH_KEY);
-	}
-
-	if (!app) {
-		ast_log(LOG_WARNING, "Could not find app '%s'\n",
-			app_name ? : "(null)");
-		return STASIS_ASR_APP_NOT_FOUND;
-	}
-
-	/* Validate the input */
-	for (i = 0; res == STASIS_ASR_OK && i < event_sources_count; ++i) {
-		if (ast_begins_with(event_source_uris[i], CHANNEL_SCHEME)) {
-			const char *channel_id = event_source_uris[i] +
-				strlen(CHANNEL_SCHEME);
-			if (!app_is_subscribed_channel_id(app, channel_id)) {
-				res = STASIS_ASR_EVENT_SOURCE_NOT_FOUND;
-			}
-		} else if (ast_begins_with(event_source_uris[i], BRIDGE_SCHEME)) {
-			const char *bridge_id = event_source_uris[i] +
-				strlen(BRIDGE_SCHEME);
-			if (!app_is_subscribed_bridge_id(app, bridge_id)) {
-				res = STASIS_ASR_EVENT_SOURCE_NOT_FOUND;
-			}
-		} else if (ast_begins_with(event_source_uris[i], ENDPOINT_SCHEME)) {
-			const char *endpoint_id = event_source_uris[i] +
-				strlen(ENDPOINT_SCHEME);
-			if (!app_is_subscribed_endpoint_id(app, endpoint_id)) {
-				res = STASIS_ASR_EVENT_SOURCE_NOT_FOUND;
-			}
-		} else {
-			res = STASIS_ASR_EVENT_SOURCE_BAD_SCHEME;
-		}
-	}
-
-	for (i = 0; res == STASIS_ASR_OK && i < event_sources_count; ++i) {
-		if (ast_begins_with(event_source_uris[i], CHANNEL_SCHEME)) {
-			const char *channel_id = event_source_uris[i] +
-				strlen(CHANNEL_SCHEME);
-			app_unsubscribe_channel_id(app, channel_id);
-		} else if (ast_begins_with(event_source_uris[i], BRIDGE_SCHEME)) {
-			const char *bridge_id = event_source_uris[i] +
-				strlen(BRIDGE_SCHEME);
-			app_unsubscribe_bridge_id(app, bridge_id);
-		} else if (ast_begins_with(event_source_uris[i], ENDPOINT_SCHEME)) {
-			const char *endpoint_id = event_source_uris[i] +
-				strlen(ENDPOINT_SCHEME);
-			app_unsubscribe_endpoint_id(app, endpoint_id);
-		}
-	}
-
-	if (res == STASIS_ASR_OK && json) {
-		*json = app_to_json(app);
-	}
-
-	return res;
+	return app_handle_subscriptions(
+		app_name, event_source_uris, event_sources_count,
+		json, app_unsubscribe);
 }
 
 void stasis_app_ref(void)
@@ -1150,6 +1201,8 @@
 
 static int unload_module(void)
 {
+	stasis_app_unregister_event_sources();
+
 	ao2_cleanup(apps_registry);
 	apps_registry = NULL;
 
@@ -1206,6 +1259,8 @@
 		return AST_MODULE_LOAD_FAILURE;
 	}
 
+	stasis_app_register_event_sources();
+
 	return AST_MODULE_LOAD_SUCCESS;
 }
 

Modified: trunk/res/stasis/app.c
URL: http://svnview.digium.com/svn/asterisk/trunk/res/stasis/app.c?view=diff&rev=403135&r1=403134&r2=403135
==============================================================================
--- trunk/res/stasis/app.c (original)
+++ trunk/res/stasis/app.c Sat Nov 23 11:48:28 2013
@@ -36,7 +36,7 @@
 #include "asterisk/stasis_endpoints.h"
 #include "asterisk/stasis_message_router.h"
 
-struct app {
+struct stasis_app {
 	/*! Aggregation topic for this application. */
 	struct stasis_topic *topic;
 	/*! Router for handling messages forwarded to \a topic. */
@@ -93,7 +93,7 @@
 	forwards->topic_cached_forward = NULL;
 }
 
-static struct app_forwards *forwards_create(struct app *app,
+static struct app_forwards *forwards_create(struct stasis_app *app,
 	const char *id)
 {
 	RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup);
@@ -114,7 +114,7 @@
 }
 
 /*! Forward a channel's topics to an app */
-static struct app_forwards *forwards_create_channel(struct app *app,
+static struct app_forwards *forwards_create_channel(struct stasis_app *app,
 	struct ast_channel *chan)
 {
 	RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup);
@@ -149,7 +149,7 @@
 }
 
 /*! Forward a bridge's topics to an app */
-static struct app_forwards *forwards_create_bridge(struct app *app,
+static struct app_forwards *forwards_create_bridge(struct stasis_app *app,
 	struct ast_bridge *bridge)
 {
 	RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup);
@@ -184,7 +184,7 @@
 }
 
 /*! Forward a endpoint's topics to an app */
-static struct app_forwards *forwards_create_endpoint(struct app *app,
+static struct app_forwards *forwards_create_endpoint(struct stasis_app *app,
 	struct ast_endpoint *endpoint)
 {
 	RAII_VAR(struct app_forwards *, forwards, NULL, ao2_cleanup);
@@ -250,7 +250,7 @@
 
 static void app_dtor(void *obj)
 {
-	struct app *app = obj;
+	struct stasis_app *app = obj;
 
 	ast_verb(1, "Destroying Stasis app %s\n", app->name);
 
@@ -268,7 +268,7 @@
 static void sub_default_handler(void *data, struct stasis_subscription *sub,
 	struct stasis_message *message)
 {
-	struct app *app = data;
+	struct stasis_app *app = data;
 	RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
 
 	if (stasis_subscription_final_message(sub, message)) {
@@ -435,7 +435,7 @@
 	struct stasis_subscription *sub,
 	struct stasis_message *message)
 {
-	struct app *app = data;

[... 588 lines stripped ...]



More information about the svn-commits mailing list