void (*loaded)(const char *object_type);
};
+/*! \brief Opaque structure for internal sorcery object */
+struct ast_sorcery_object;
+
/*! \brief Structure which contains details about a sorcery object */
struct ast_sorcery_object_details {
- /*! \brief Unique identifier of this object */
- char id[AST_UUID_STR_LEN];
-
- /*! \brief Type of object */
- char type[MAX_OBJECT_TYPE];
+ /*! \brief Pointer to internal sorcery object information */
+ struct ast_sorcery_object *object;
};
/*! \brief Macro which must be used at the beginning of each sorcery capable object */
*/
int ast_sorcery_changeset_create(const struct ast_variable *original, const struct ast_variable *modified, struct ast_variable **changes);
+/*!
+ * \brief Allocate a generic sorcery capable object
+ *
+ * \param size Size of the object
+ * \param destructor Optional destructor function
+ *
+ * \retval non-NULL success
+ * \retval NULL failure
+ */
+void *ast_sorcery_generic_alloc(size_t size, ao2_destructor_fn destructor);
+
/*!
* \brief Allocate an object
*
*/
const char *ast_sorcery_object_get_type(const void *object);
+/*!
+ * \brief Get an extended field value from a sorcery object
+ *
+ * \param object Pointer to a sorcery object
+ * \param name Name of the extended field value
+ *
+ * \retval non-NULL if found
+ * \retval NULL if not found
+ *
+ * \note The returned string does NOT need to be freed and is guaranteed to remain valid for the lifetime of the object
+ */
+const char *ast_sorcery_object_get_extended(const void *object, const char *name);
+
+/*!
+ * \brief Set an extended field value on a sorcery object
+ *
+ * \param object Pointer to a sorcery object
+ * \param name Name of the extended field
+ * \param value Value of the extended field
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ *
+ * \note The field name MUST begin with '@' to indicate it is an extended field.
+ * \note If the extended field already exists it will be overwritten with the new value.
+ */
+int ast_sorcery_object_set_extended(const void *object, const char *name, const char *value);
+
#if defined(__cplusplus) || defined(c_plusplus)
}
#endif
/*! \brief Thread pool for observers */
static struct ast_threadpool *threadpool;
+/*! \brief Structure for internal sorcery object information */
+struct ast_sorcery_object {
+ /*! \brief Unique identifier of this object */
+ char id[AST_UUID_STR_LEN];
+
+ /*! \brief Type of object */
+ char type[MAX_OBJECT_TYPE];
+
+ /*! \brief Optional object destructor */
+ ao2_destructor_fn destructor;
+
+ /*! \brief Extended object fields */
+ struct ast_variable *extended;
+};
+
/*! \brief Structure for registered object type */
struct ast_sorcery_object_type {
/*! \brief Unique name of the object type */
return sorcery_apply_wizard_mapping(sorcery, type, module, name, data, 0);
}
+static int sorcery_extended_config_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
+{
+ return ast_sorcery_object_set_extended(obj, var->name, var->value);
+}
+
+static int sorcery_extended_fields_handler(const void *obj, struct ast_variable **fields)
+{
+ const struct ast_sorcery_object_details *details = obj;
+
+ if (details->object->extended) {
+ *fields = ast_variables_dup(details->object->extended);
+ } else {
+ *fields = NULL;
+ }
+
+ return 0;
+}
+
int ast_sorcery_object_register(struct ast_sorcery *sorcery, const char *type, aco_type_item_alloc alloc, sorcery_transform_handler transform, sorcery_apply_handler apply)
{
RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, type, OBJ_KEY), ao2_cleanup);
return -1;
}
+ if (ast_sorcery_object_fields_register(sorcery, type, "^@", sorcery_extended_config_handler, sorcery_extended_fields_handler)) {
+ return -1;
+ }
+
return 0;
}
struct ast_variable *ast_sorcery_objectset_create(const struct ast_sorcery *sorcery, const void *object)
{
const struct ast_sorcery_object_details *details = object;
- RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, details->type, OBJ_KEY), ao2_cleanup);
+ RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, details->object->type, OBJ_KEY), ao2_cleanup);
struct ao2_iterator i;
struct ast_sorcery_object_field *object_field;
struct ast_variable *fields = NULL;
continue;
}
- if (!res) {
+ if (!res && tmp) {
tmp->next = fields;
fields = tmp;
}
struct ast_json *ast_sorcery_objectset_json_create(const struct ast_sorcery *sorcery, const void *object)
{
const struct ast_sorcery_object_details *details = object;
- RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, details->type, OBJ_KEY), ao2_cleanup);
+ RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, details->object->type, OBJ_KEY), ao2_cleanup);
struct ao2_iterator i;
struct ast_sorcery_object_field *object_field;
struct ast_json *json = ast_json_object_create();
int ast_sorcery_objectset_apply(const struct ast_sorcery *sorcery, void *object, struct ast_variable *objectset)
{
const struct ast_sorcery_object_details *details = object;
- RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, details->type, OBJ_KEY), ao2_cleanup);
+ RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, details->object->type, OBJ_KEY), ao2_cleanup);
RAII_VAR(struct ast_variable *, transformed, NULL, ast_variables_destroy);
struct ast_variable *field;
int res = 0;
}
for (; field; field = field->next) {
- if ((res = aco_process_var(&object_type->type, details->id, field, object))) {
+ if ((res = aco_process_var(&object_type->type, details->object->id, field, object))) {
break;
}
}
return res;
}
+static void sorcery_object_destructor(void *object)
+{
+ struct ast_sorcery_object_details *details = object;
+
+ if (details->object->destructor) {
+ details->object->destructor(object);
+ }
+
+ ast_variables_destroy(details->object->extended);
+}
+
+void *ast_sorcery_generic_alloc(size_t size, ao2_destructor_fn destructor)
+{
+ void *object = ao2_alloc_options(size + sizeof(struct ast_sorcery_object), sorcery_object_destructor, AO2_ALLOC_OPT_LOCK_NOLOCK);
+ struct ast_sorcery_object_details *details = object;
+
+ if (!object) {
+ return NULL;
+ }
+
+ details->object = object + size;
+ details->object->destructor = destructor;
+
+ return object;
+}
+
void *ast_sorcery_alloc(const struct ast_sorcery *sorcery, const char *type, const char *id)
{
RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, type, OBJ_KEY), ao2_cleanup);
}
if (ast_strlen_zero(id)) {
- ast_uuid_generate_str(details->id, sizeof(details->id));
+ ast_uuid_generate_str(details->object->id, sizeof(details->object->id));
} else {
- ast_copy_string(details->id, id, sizeof(details->id));
+ ast_copy_string(details->object->id, id, sizeof(details->object->id));
}
- ast_copy_string(details->type, type, sizeof(details->type));
+ ast_copy_string(details->object->type, type, sizeof(details->object->type));
if (aco_set_defaults(&object_type->type, id, details)) {
ao2_ref(details, -1);
void *ast_sorcery_copy(const struct ast_sorcery *sorcery, const void *object)
{
const struct ast_sorcery_object_details *details = object;
- RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, details->type, OBJ_KEY), ao2_cleanup);
- struct ast_sorcery_object_details *copy = ast_sorcery_alloc(sorcery, details->type, details->id);
+ RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, details->object->type, OBJ_KEY), ao2_cleanup);
+ struct ast_sorcery_object_details *copy = ast_sorcery_alloc(sorcery, details->object->type, details->object->id);
RAII_VAR(struct ast_variable *, objectset, NULL, ast_variables_destroy);
int res = 0;
unsigned int cached = 0;
if (!object_type) {
- ast_log(LOG_NOTICE, "Can't find object type '%s'\n", type);
return NULL;
}
int ast_sorcery_create(const struct ast_sorcery *sorcery, void *object)
{
const struct ast_sorcery_object_details *details = object;
- RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, details->type, OBJ_KEY), ao2_cleanup);
+ RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, details->object->type, OBJ_KEY), ao2_cleanup);
RAII_VAR(struct ast_sorcery_object_wizard *, object_wizard, NULL, ao2_cleanup);
struct sorcery_details sdetails = {
.sorcery = sorcery,
int ast_sorcery_update(const struct ast_sorcery *sorcery, void *object)
{
const struct ast_sorcery_object_details *details = object;
- RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, details->type, OBJ_KEY), ao2_cleanup);
+ RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, details->object->type, OBJ_KEY), ao2_cleanup);
RAII_VAR(struct ast_sorcery_object_wizard *, object_wizard, NULL, ao2_cleanup);
struct sorcery_details sdetails = {
.sorcery = sorcery,
int ast_sorcery_delete(const struct ast_sorcery *sorcery, void *object)
{
const struct ast_sorcery_object_details *details = object;
- RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, details->type, OBJ_KEY), ao2_cleanup);
+ RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, details->object->type, OBJ_KEY), ao2_cleanup);
RAII_VAR(struct ast_sorcery_object_wizard *, object_wizard, NULL, ao2_cleanup);
struct sorcery_details sdetails = {
.sorcery = sorcery,
const char *ast_sorcery_object_get_id(const void *object)
{
const struct ast_sorcery_object_details *details = object;
- return details->id;
+ return details->object->id;
}
const char *ast_sorcery_object_get_type(const void *object)
{
const struct ast_sorcery_object_details *details = object;
- return details->type;
+ return details->object->type;
+}
+
+const char *ast_sorcery_object_get_extended(const void *object, const char *name)
+{
+ const struct ast_sorcery_object_details *details = object;
+ struct ast_variable *field;
+
+ for (field = details->object->extended; field; field = field->next) {
+ if (!strcmp(field->name + 1, name)) {
+ return field->value;
+ }
+ }
+
+ return NULL;
+}
+
+int ast_sorcery_object_set_extended(const void *object, const char *name, const char *value)
+{
+ RAII_VAR(struct ast_variable *, field, NULL, ast_variables_destroy);
+ struct ast_variable *extended = ast_variable_new(name, value, ""), *previous = NULL;
+ const struct ast_sorcery_object_details *details = object;
+
+ if (!extended) {
+ return -1;
+ }
+
+ for (field = details->object->extended; field; previous = field, field = field->next) {
+ if (!strcmp(field->name, name)) {
+ if (previous) {
+ previous->next = field->next;
+ } else {
+ details->object->extended = field->next;
+ }
+ field->next = NULL;
+ break;
+ }
+ }
+
+ extended->next = details->object->extended;
+ details->object->extended = extended;
+
+ return 0;
}
int ast_sorcery_observer_add(const struct ast_sorcery *sorcery, const char *type, const struct ast_sorcery_observer *callbacks)
/*! \brief Internal function to allocate a test object */
static void *test_sorcery_object_alloc(const char *id)
{
- return ao2_alloc(sizeof(struct test_sorcery_object), NULL);
+ return ast_sorcery_generic_alloc(sizeof(struct test_sorcery_object), NULL);
}
/*! \brief Internal function for object set transformation */
return res;
}
+AST_TEST_DEFINE(extended_fields)
+{
+ int res = AST_TEST_PASS;
+ RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref);
+ RAII_VAR(struct test_sorcery_object *, obj, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_variable *, objset, NULL, ast_variables_destroy);
+ const char *value;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "extended_fields";
+ info->category = "/main/sorcery/";
+ info->summary = "sorcery object extended fields unit test";
+ info->description =
+ "Test extended fields support in sorcery";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ if (!(sorcery = alloc_and_initialize_sorcery())) {
+ ast_test_status_update(test, "Failed to open sorcery structure\n");
+ return AST_TEST_FAIL;
+ }
+
+ if (!(obj = ast_sorcery_alloc(sorcery, "test", "blah"))) {
+ ast_test_status_update(test, "Failed to allocate a known object type\n");
+ return AST_TEST_FAIL;
+ }
+
+ if (!(objset = ast_variable_new("@testing", "toast", ""))) {
+ ast_test_status_update(test, "Failed to create an object set, test could not occur\n");
+ res = AST_TEST_FAIL;
+ } else if (ast_sorcery_objectset_apply(sorcery, obj, objset)) {
+ ast_test_status_update(test, "Failed to apply valid object set to object\n");
+ res = AST_TEST_FAIL;
+ } else if (!(value = ast_sorcery_object_get_extended(obj, "testing"))) {
+ ast_test_status_update(test, "Extended field, which was set using object set, could not be found\n");
+ res = AST_TEST_FAIL;
+ } else if (strcmp(value, "toast")) {
+ ast_test_status_update(test, "Extended field does not contain expected value\n");
+ res = AST_TEST_FAIL;
+ } else if (ast_sorcery_object_set_extended(obj, "@tacos", "supreme")) {
+ ast_test_status_update(test, "Extended field could not be set\n");
+ res = AST_TEST_FAIL;
+ } else if (!(value = ast_sorcery_object_get_extended(obj, "tacos"))) {
+ ast_test_status_update(test, "Extended field, which was set using the API, could not be found\n");
+ res = AST_TEST_FAIL;
+ } else if (strcmp(value, "supreme")) {
+ ast_test_status_update(test, "Extended field does not contain expected value\n");
+ res = AST_TEST_FAIL;
+ } else if (ast_sorcery_object_set_extended(obj, "@tacos", "canadian")) {
+ ast_test_status_update(test, "Extended field could not be set a second time\n");
+ res = AST_TEST_FAIL;
+ } else if (!(value = ast_sorcery_object_get_extended(obj, "tacos"))) {
+ ast_test_status_update(test, "Extended field, which was set using the API, could not be found\n");
+ res = AST_TEST_FAIL;
+ } else if (strcmp(value, "canadian")) {
+ ast_test_status_update(test, "Extended field does not contain expected value\n");
+ res = AST_TEST_FAIL;
+ }
+
+ return res;
+}
+
AST_TEST_DEFINE(changeset_create)
{
int res = AST_TEST_PASS;
AST_TEST_UNREGISTER(objectset_apply_invalid);
AST_TEST_UNREGISTER(objectset_transform);
AST_TEST_UNREGISTER(objectset_apply_fields);
+ AST_TEST_UNREGISTER(extended_fields);
AST_TEST_UNREGISTER(changeset_create);
AST_TEST_UNREGISTER(changeset_create_unchanged);
AST_TEST_UNREGISTER(object_create);
AST_TEST_REGISTER(objectset_apply_invalid);
AST_TEST_REGISTER(objectset_transform);
AST_TEST_REGISTER(objectset_apply_fields);
+ AST_TEST_REGISTER(extended_fields);
AST_TEST_REGISTER(changeset_create);
AST_TEST_REGISTER(changeset_create_unchanged);
AST_TEST_REGISTER(object_create);