Add the bucket API.
[asterisk/asterisk.git] / main / sorcery.c
index f9ff6d5..1bd55d4 100644 (file)
@@ -38,6 +38,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/config_options.h"
 #include "asterisk/netsock2.h"
 #include "asterisk/module.h"
+#include "asterisk/taskprocessor.h"
+#include "asterisk/threadpool.h"
+#include "asterisk/json.h"
 
 /* To prevent DEBUG_FD_LEAKS from interfering with things we undef open and close */
 #undef open
@@ -52,6 +55,24 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 /*! \brief Maximum length of an object field name */
 #define MAX_OBJECT_FIELD 128
 
+/*! \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;
+
+       /*! \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 */
@@ -83,6 +104,27 @@ struct ast_sorcery_object_type {
 
        /*! \brief Type details */
        struct aco_type type;
+
+       /*! \brief Observers */
+       struct ao2_container *observers;
+
+       /*! \brief Serializer for observers */
+       struct ast_taskprocessor *serializer;
+};
+
+/*! \brief Structure for registered object type observer */
+struct ast_sorcery_object_type_observer {
+       /*! \brief Pointer to the observer implementation */
+       const struct ast_sorcery_observer *callbacks;
+};
+
+/*! \brief Structure used for observer invocations */
+struct sorcery_observer_invocation {
+       /*! \brief Pointer to the object type */
+       struct ast_sorcery_object_type *object_type;
+
+       /*! \brief Pointer to the object */
+       void *object;
 };
 
 /*! \brief Structure for registered object field */
@@ -90,9 +132,12 @@ struct ast_sorcery_object_field {
        /*! \brief Name of the field */
        char name[MAX_OBJECT_FIELD];
 
-       /*! \brief Callback function for translation */
+       /*! \brief Callback function for translation of a single value */
        sorcery_field_handler handler;
 
+       /*! \brief Callback function for translation of multiple values */
+       sorcery_fields_handler multiple_handler;
+
        /*! \brief Position of the field */
        intptr_t args[];
 };
@@ -166,11 +211,6 @@ static int sockaddr_handler_fn(const void *obj, const intptr_t *args, char **buf
        return !(*buf = ast_strdup(ast_sockaddr_stringify(field))) ? -1 : 0;
 }
 
-static int noop_handler_fn(const void *obj, const intptr_t *args, char **buf)
-{
-       return 0;
-}
-
 static int chararray_handler_fn(const void *obj, const intptr_t *args, char **buf)
 {
        char *field = (char *)(obj + args[0]);
@@ -184,7 +224,6 @@ static sorcery_field_handler sorcery_field_default_handler(enum aco_option_type
        case OPT_CHAR_ARRAY_T: return chararray_handler_fn;
        case OPT_DOUBLE_T: return double_handler_fn;
        case OPT_INT_T: return int_handler_fn;
-       case OPT_NOOP_T: return noop_handler_fn;
        case OPT_SOCKADDR_T: return sockaddr_handler_fn;
        case OPT_STRINGFIELD_T: return stringfield_handler_fn;
        case OPT_UINT_T: return uint_handler_fn;
@@ -214,14 +253,43 @@ static int sorcery_wizard_cmp(void *obj, void *arg, int flags)
        return !strcmp(wizard1->name, flags & OBJ_KEY ? name : wizard2->name) ? CMP_MATCH | CMP_STOP : 0;
 }
 
+/*! \brief Cleanup function */
+static void sorcery_exit(void)
+{
+       ast_threadpool_shutdown(threadpool);
+       threadpool = NULL;
+}
+
+/*! \brief Cleanup function for graceful shutdowns */
+static void sorcery_cleanup(void)
+{
+       ao2_cleanup(wizards);
+}
+
 int ast_sorcery_init(void)
 {
+       struct ast_threadpool_options options = {
+               .version = AST_THREADPOOL_OPTIONS_VERSION,
+               .auto_increment = 1,
+               .max_size = 0,
+               .idle_timeout = 60,
+               .initial_size = 0,
+       };
        ast_assert(wizards == NULL);
 
+       if (!(threadpool = ast_threadpool_create("Sorcery", NULL, &options))) {
+               threadpool = NULL;
+               return -1;
+       }
+
        if (!(wizards = ao2_container_alloc(WIZARD_BUCKETS, sorcery_wizard_hash, sorcery_wizard_cmp))) {
+               ast_threadpool_shutdown(threadpool);
                return -1;
        }
 
+       ast_register_cleanup(sorcery_cleanup);
+       ast_register_atexit(sorcery_exit);
+
        return 0;
 }
 
@@ -320,6 +388,7 @@ static void sorcery_object_type_destructor(void *obj)
 
        ao2_cleanup(object_type->wizards);
        ao2_cleanup(object_type->fields);
+       ao2_cleanup(object_type->observers);
 
        if (object_type->info) {
                aco_info_destroy(object_type->info);
@@ -327,12 +396,15 @@ static void sorcery_object_type_destructor(void *obj)
        }
 
        ast_free(object_type->file);
+
+       ast_taskprocessor_unreference(object_type->serializer);
 }
 
 /*! \brief Internal function which allocates an object type structure */
-static struct ast_sorcery_object_type *sorcery_object_type_alloc(const char *type)
+static struct ast_sorcery_object_type *sorcery_object_type_alloc(const char *type, const char *module)
 {
        struct ast_sorcery_object_type *object_type;
+       char uuid[AST_UUID_STR_LEN];
 
        if (!(object_type = ao2_alloc(sizeof(*object_type), sorcery_object_type_destructor))) {
                return NULL;
@@ -349,6 +421,11 @@ static struct ast_sorcery_object_type *sorcery_object_type_alloc(const char *typ
                return NULL;
        }
 
+       if (!(object_type->observers = ao2_container_alloc_options(AO2_ALLOC_OPT_LOCK_RWLOCK, 1, NULL, NULL))) {
+               ao2_ref(object_type, -1);
+               return NULL;
+       }
+
        if (!(object_type->info = ast_calloc(1, sizeof(*object_type->info) + 2 * sizeof(object_type->info->files[0])))) {
                ao2_ref(object_type, -1);
                return NULL;
@@ -359,8 +436,19 @@ static struct ast_sorcery_object_type *sorcery_object_type_alloc(const char *typ
                return NULL;
        }
 
+       if (!ast_uuid_generate_str(uuid, sizeof(uuid))) {
+               ao2_ref(object_type, -1);
+               return NULL;
+       }
+
+       if (!(object_type->serializer = ast_threadpool_serializer(uuid, threadpool))) {
+               ao2_ref(object_type, -1);
+               return NULL;
+       }
+
        object_type->info->files[0] = object_type->file;
        object_type->info->files[1] = NULL;
+       object_type->info->module = module;
 
        ast_copy_string(object_type->name, type, sizeof(object_type->name));
 
@@ -382,7 +470,7 @@ static void sorcery_object_wizard_destructor(void *obj)
 }
 
 /*! \brief Internal function which creates an object type and adds a wizard mapping */
-static int sorcery_apply_wizard_mapping(struct ast_sorcery *sorcery, const char *type, const char *name, const char *data, unsigned int caching)
+static int sorcery_apply_wizard_mapping(struct ast_sorcery *sorcery, const char *type, const char *module, const char *name, const char *data, unsigned int caching)
 {
        RAII_VAR(struct ast_sorcery_object_type *, object_type,  ao2_find(sorcery->types, type, OBJ_KEY), ao2_cleanup);
        RAII_VAR(struct ast_sorcery_wizard *, wizard, ao2_find(wizards, name, OBJ_KEY), ao2_cleanup);
@@ -394,7 +482,7 @@ static int sorcery_apply_wizard_mapping(struct ast_sorcery *sorcery, const char
        }
 
        if (!object_type) {
-               if (!(object_type = sorcery_object_type_alloc(type))) {
+               if (!(object_type = sorcery_object_type_alloc(type, module))) {
                        return -1;
                }
                created = 1;
@@ -418,7 +506,7 @@ static int sorcery_apply_wizard_mapping(struct ast_sorcery *sorcery, const char
        return 0;
 }
 
-int ast_sorcery_apply_config(struct ast_sorcery *sorcery, const char *name)
+int __ast_sorcery_apply_config(struct ast_sorcery *sorcery, const char *name, const char *module)
 {
        struct ast_flags flags = { 0 };
        struct ast_config *config = ast_config_load2("sorcery.conf", "sorcery", flags);
@@ -447,7 +535,7 @@ int ast_sorcery_apply_config(struct ast_sorcery *sorcery, const char *name)
                }
 
                /* Any error immediately causes us to stop */
-               if ((res = sorcery_apply_wizard_mapping(sorcery, name, wizard, data, caching))) {
+               if ((res = sorcery_apply_wizard_mapping(sorcery, name, module, wizard, data, caching))) {
                        break;
                }
        }
@@ -457,7 +545,7 @@ int ast_sorcery_apply_config(struct ast_sorcery *sorcery, const char *name)
        return res;
 }
 
-int ast_sorcery_apply_default(struct ast_sorcery *sorcery, const char *type, const char *name, const char *data)
+int __ast_sorcery_apply_default(struct ast_sorcery *sorcery, const char *type, const char *module, const char *name, const char *data)
 {
        RAII_VAR(struct ast_sorcery_object_type *, object_type,  ao2_find(sorcery->types, type, OBJ_KEY), ao2_cleanup);
 
@@ -466,10 +554,28 @@ int ast_sorcery_apply_default(struct ast_sorcery *sorcery, const char *type, con
                return -1;
        }
 
-       return sorcery_apply_wizard_mapping(sorcery, type, name, data, 0);
+       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)
+int __ast_sorcery_object_register(struct ast_sorcery *sorcery, const char *type, unsigned int hidden, 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);
 
@@ -477,9 +583,11 @@ int ast_sorcery_object_register(struct ast_sorcery *sorcery, const char *type, a
                return -1;
        }
 
+       object_type->type.name = object_type->name;
        object_type->type.type = ACO_ITEM;
-       object_type->type.category = "";
+       object_type->type.category = ".?";
        object_type->type.item_alloc = alloc;
+       object_type->type.hidden = hidden;
 
        object_type->transform = transform;
        object_type->apply = apply;
@@ -490,6 +598,10 @@ int ast_sorcery_object_register(struct ast_sorcery *sorcery, const char *type, a
                return -1;
        }
 
+       if (ast_sorcery_object_fields_register(sorcery, type, "^@", sorcery_extended_config_handler, sorcery_extended_fields_handler)) {
+               return -1;
+       }
+
        return 0;
 }
 
@@ -515,15 +627,33 @@ void ast_sorcery_object_set_diff_handler(struct ast_sorcery *sorcery, const char
        object_type->diff = diff;
 }
 
+int ast_sorcery_object_fields_register(struct ast_sorcery *sorcery, const char *type, const char *regex, aco_option_handler config_handler, sorcery_fields_handler sorcery_handler)
+{
+       RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, type, OBJ_KEY), ao2_cleanup);
+       RAII_VAR(struct ast_sorcery_object_field *, object_field, NULL, ao2_cleanup);
+
+       if (!object_type || !object_type->type.item_alloc || !config_handler || !(object_field = ao2_alloc(sizeof(*object_field), NULL))) {
+               return -1;
+       }
+
+       ast_copy_string(object_field->name, regex, sizeof(object_field->name));
+       object_field->multiple_handler = sorcery_handler;
+
+       ao2_link(object_type->fields, object_field);
+       __aco_option_register(object_type->info, regex, ACO_REGEX, object_type->file->types, "", OPT_CUSTOM_T, config_handler, 0, 1, 0);
+
+       return 0;
+}
+
 int __ast_sorcery_object_field_register(struct ast_sorcery *sorcery, const char *type, const char *name, const char *default_val, enum aco_option_type opt_type,
-                                       aco_option_handler config_handler, sorcery_field_handler sorcery_handler, unsigned int flags, size_t argc, ...)
+                                       aco_option_handler config_handler, sorcery_field_handler sorcery_handler, unsigned int flags, unsigned int no_doc, size_t argc, ...)
 {
        RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, type, OBJ_KEY), ao2_cleanup);
        RAII_VAR(struct ast_sorcery_object_field *, object_field, NULL, ao2_cleanup);
        int pos;
        va_list args;
 
-       if (!object_type || !object_type->type.item_alloc) {
+       if (!strcmp(type, "id") || !object_type || !object_type->type.item_alloc) {
                return -1;
        }
 
@@ -548,15 +678,15 @@ int __ast_sorcery_object_field_register(struct ast_sorcery *sorcery, const char
 
        /* TODO: Improve this hack */
        if (!argc) {
-               __aco_option_register(object_type->info, name, ACO_EXACT, object_type->file->types, default_val, opt_type, config_handler, flags, argc);
+               __aco_option_register(object_type->info, name, ACO_EXACT, object_type->file->types, default_val, opt_type, config_handler, flags, no_doc, argc);
        } else if (argc == 1) {
-               __aco_option_register(object_type->info, name, ACO_EXACT, object_type->file->types, default_val, opt_type, config_handler, flags, argc,
+               __aco_option_register(object_type->info, name, ACO_EXACT, object_type->file->types, default_val, opt_type, config_handler, flags, no_doc, argc,
                                      object_field->args[0]);
        } else if (argc == 2) {
-               __aco_option_register(object_type->info, name, ACO_EXACT, object_type->file->types, default_val, opt_type, config_handler, flags, argc,
+               __aco_option_register(object_type->info, name, ACO_EXACT, object_type->file->types, default_val, opt_type, config_handler, flags, no_doc, argc,
                                      object_field->args[0], object_field->args[1]);
        } else if (argc == 3) {
-               __aco_option_register(object_type->info, name, ACO_EXACT, object_type->file->types, default_val, opt_type, config_handler, flags, argc,
+               __aco_option_register(object_type->info, name, ACO_EXACT, object_type->file->types, default_val, opt_type, config_handler, flags, no_doc, argc,
                                      object_field->args[0], object_field->args[1], object_field->args[2]);
        } else {
                ast_assert(0); /* The hack... she does us no good for this */
@@ -580,6 +710,58 @@ static int sorcery_wizard_load(void *obj, void *arg, int flags)
        return 0;
 }
 
+/*! \brief Destructor for observer invocation */
+static void sorcery_observer_invocation_destroy(void *obj)
+{
+       struct sorcery_observer_invocation *invocation = obj;
+
+       ao2_cleanup(invocation->object_type);
+       ao2_cleanup(invocation->object);
+}
+
+/*! \brief Allocator function for observer invocation */
+static struct sorcery_observer_invocation *sorcery_observer_invocation_alloc(struct ast_sorcery_object_type *object_type, void *object)
+{
+       struct sorcery_observer_invocation *invocation = ao2_alloc(sizeof(*invocation), sorcery_observer_invocation_destroy);
+
+       if (!invocation) {
+               return NULL;
+       }
+
+       ao2_ref(object_type, +1);
+       invocation->object_type = object_type;
+
+       if (object) {
+               ao2_ref(object, +1);
+               invocation->object = object;
+       }
+
+       return invocation;
+}
+
+/*! \brief Internal callback function which notifies an individual observer that an object type has been loaded */
+static int sorcery_observer_notify_loaded(void *obj, void *arg, int flags)
+{
+       const struct ast_sorcery_object_type_observer *observer = obj;
+
+       if (observer->callbacks->loaded) {
+               observer->callbacks->loaded(arg);
+       }
+
+       return 0;
+}
+
+/*! \brief Internal callback function which notifies observers that an object type has been loaded */
+static int sorcery_observers_notify_loaded(void *data)
+{
+       struct sorcery_observer_invocation *invocation = data;
+
+       ao2_callback(invocation->object_type->observers, OBJ_NODATA, sorcery_observer_notify_loaded, invocation->object_type->name);
+       ao2_cleanup(invocation);
+
+       return 0;
+}
+
 static int sorcery_object_load(void *obj, void *arg, int flags)
 {
        struct ast_sorcery_object_type *type = obj;
@@ -588,6 +770,14 @@ static int sorcery_object_load(void *obj, void *arg, int flags)
        details->type = type->name;
        ao2_callback(type->wizards, OBJ_NODATA, sorcery_wizard_load, details);
 
+       if (ao2_container_count(type->observers)) {
+               struct sorcery_observer_invocation *invocation = sorcery_observer_invocation_alloc(type, NULL);
+
+               if (invocation && ast_taskprocessor_push(type->serializer, sorcery_observers_notify_loaded, invocation)) {
+                       ao2_cleanup(invocation);
+               }
+       }
+
        return 0;
 }
 
@@ -649,7 +839,7 @@ void ast_sorcery_ref(struct ast_sorcery *sorcery)
 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;
@@ -661,24 +851,30 @@ struct ast_variable *ast_sorcery_objectset_create(const struct ast_sorcery *sorc
 
        i = ao2_iterator_init(object_type->fields, 0);
 
-       for (; (object_field = ao2_iterator_next(&i)); ao2_ref(object_field, -1)) {
-               RAII_VAR(char *, buf, NULL, ast_free);
-               struct ast_variable *tmp;
+       for (; (object_field = ao2_iterator_next(&i)) && !res; ao2_ref(object_field, -1)) {
+               struct ast_variable *tmp = NULL;
 
-               /* Any fields with no handler just get skipped */
-               if (!object_field->handler) {
+               if (object_field->multiple_handler) {
+                       if ((res = object_field->multiple_handler(object, &tmp))) {
+                               ast_variables_destroy(tmp);
+                       }
+               } else if (object_field->handler) {
+                       char *buf = NULL;
+
+                       if ((res = object_field->handler(object, object_field->args, &buf)) ||
+                               !(tmp = ast_variable_new(object_field->name, S_OR(buf, ""), ""))) {
+                               res = -1;
+                       }
+
+                       ast_free(buf);
+               } else {
                        continue;
                }
 
-               if ((res = object_field->handler(object, object_field->args, &buf)) ||
-                   !(tmp = ast_variable_new(object_field->name, S_OR(buf, ""), ""))) {
-                       res = -1;
-                       ao2_ref(object_field, -1);
-                       break;
+               if (!res && tmp) {
+                       tmp->next = fields;
+                       fields = tmp;
                }
-
-               tmp->next = fields;
-               fields = tmp;
        }
 
        ao2_iterator_destroy(&i);
@@ -692,10 +888,72 @@ struct ast_variable *ast_sorcery_objectset_create(const struct ast_sorcery *sorc
        return fields;
 }
 
+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->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 res = 0;
+
+       if (!object_type || !json) {
+               return NULL;
+       }
+
+       i = ao2_iterator_init(object_type->fields, 0);
+
+       for (; (object_field = ao2_iterator_next(&i)) && !res; ao2_ref(object_field, -1)) {
+               if (object_field->multiple_handler) {
+                       struct ast_variable *tmp = NULL;
+                       struct ast_variable *field;
+
+                       if ((res = object_field->multiple_handler(object, &tmp))) {
+                               break;
+                       }
+
+                       for (field = tmp; field; field = field->next) {
+                               struct ast_json *value = ast_json_string_create(field->value);
+
+                               if (value && ast_json_object_set(json, field->name, value)) {
+                                       ast_json_unref(value);
+                                       res = -1;
+                               }
+                       }
+
+                       ast_variables_destroy(tmp);
+               } else if (object_field->handler) {
+                       char *buf = NULL;
+                       struct ast_json *value = NULL;
+
+                       if ((res = object_field->handler(object, object_field->args, &buf)) ||
+                               !(value = ast_json_string_create(buf)) ||
+                               ast_json_object_set(json, object_field->name, value)) {
+                               ast_json_unref(value);
+                               res = -1;
+                       }
+
+                       ast_free(buf);
+               } else {
+                       continue;
+               }
+       }
+
+       ao2_iterator_destroy(&i);
+
+       /* If any error occurs we destroy the JSON object so a partial objectset is not returned */
+       if (res) {
+               ast_json_unref(json);
+               json = NULL;
+       }
+
+       return json;
+}
+
 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;
@@ -711,13 +969,13 @@ int ast_sorcery_objectset_apply(const struct ast_sorcery *sorcery, void *object,
        }
 
        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;
                }
        }
 
        if (!res && object_type->apply) {
-               object_type->apply(sorcery, object);
+               res = object_type->apply(sorcery, object);
        }
 
        return res;
@@ -774,31 +1032,53 @@ int ast_sorcery_changeset_create(const struct ast_variable *original, const stru
        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);
+       ast_free(details->object->id);
+}
+
+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);
        struct ast_sorcery_object_details *details;
 
        if (!object_type || !object_type->type.item_alloc ||
-           !(details = object_type->type.item_alloc(""))) {
+           !(details = object_type->type.item_alloc(id))) {
                return NULL;
        }
 
        if (ast_strlen_zero(id)) {
-               struct ast_uuid *uuid = ast_uuid_generate();
+               char uuid[AST_UUID_STR_LEN];
 
-               if (!uuid) {
-                       ao2_ref(details, -1);
-                       return NULL;
-               }
-
-               ast_uuid_to_str(uuid, details->id, AST_UUID_STR_LEN);
-               ast_free(uuid);
+               ast_uuid_generate_str(uuid, sizeof(uuid));
+               details->object->id = ast_strdup(uuid);
        } else {
-               ast_copy_string(details->id, id, sizeof(details->id));
+               details->object->id = ast_strdup(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);
@@ -811,8 +1091,8 @@ void *ast_sorcery_alloc(const struct ast_sorcery *sorcery, const char *type, con
 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;
 
@@ -860,16 +1140,25 @@ int ast_sorcery_diff(const struct ast_sorcery *sorcery, const void *original, co
        }
 }
 
+/*! \brief Structure used when calling create, update, or delete */
+struct sorcery_details {
+       /*! \brief Pointer to the sorcery instance */
+       const struct ast_sorcery *sorcery;
+       /*! \brief Pointer to the object itself */
+       void *obj;
+};
+
 /*! \brief Internal function used to create an object in caching wizards */
 static int sorcery_cache_create(void *obj, void *arg, int flags)
 {
-       struct ast_sorcery_object_wizard *object_wizard = obj;
+       const struct ast_sorcery_object_wizard *object_wizard = obj;
+       const struct sorcery_details *details = arg;
 
        if (!object_wizard->caching || !object_wizard->wizard->create) {
                return 0;
        }
 
-       object_wizard->wizard->create(object_wizard->data, arg);
+       object_wizard->wizard->create(details->sorcery, object_wizard->data, details->obj);
 
        return 0;
 }
@@ -958,73 +1247,202 @@ void *ast_sorcery_retrieve_by_fields(const struct ast_sorcery *sorcery, const ch
        return object;
 }
 
+struct ao2_container *ast_sorcery_retrieve_by_regex(const struct ast_sorcery *sorcery, const char *type, const char *regex)
+{
+       RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, type, OBJ_KEY), ao2_cleanup);
+       struct ao2_container *objects;
+       struct ao2_iterator i;
+       struct ast_sorcery_object_wizard *wizard;
+
+       if (!object_type || !(objects = ao2_container_alloc_options(AO2_ALLOC_OPT_LOCK_NOLOCK, 1, NULL, NULL))) {
+               return NULL;
+       }
+
+       i = ao2_iterator_init(object_type->wizards, 0);
+       for (; (wizard = ao2_iterator_next(&i)); ao2_ref(wizard, -1)) {
+               if (!wizard->wizard->retrieve_regex) {
+                       continue;
+               }
+
+               wizard->wizard->retrieve_regex(sorcery, wizard->data, object_type->name, objects, regex);
+       }
+       ao2_iterator_destroy(&i);
+
+       return objects;
+}
+
 /*! \brief Internal function which returns if the wizard has created the object */
 static int sorcery_wizard_create(void *obj, void *arg, int flags)
 {
        const struct ast_sorcery_object_wizard *object_wizard = obj;
+       const struct sorcery_details *details = arg;
+
+       return (!object_wizard->caching && !object_wizard->wizard->create(details->sorcery, object_wizard->data, details->obj)) ? CMP_MATCH | CMP_STOP : 0;
+}
+
+/*! \brief Internal callback function which notifies an individual observer that an object has been created */
+static int sorcery_observer_notify_create(void *obj, void *arg, int flags)
+{
+       const struct ast_sorcery_object_type_observer *observer = obj;
+
+       if (observer->callbacks->created) {
+               observer->callbacks->created(arg);
+       }
+
+       return 0;
+}
+
+/*! \brief Internal callback function which notifies observers that an object has been created */
+static int sorcery_observers_notify_create(void *data)
+{
+       struct sorcery_observer_invocation *invocation = data;
+
+       ao2_callback(invocation->object_type->observers, OBJ_NODATA, sorcery_observer_notify_create, invocation->object);
+       ao2_cleanup(invocation);
 
-       return (!object_wizard->caching && !object_wizard->wizard->create(object_wizard->data, arg)) ? CMP_MATCH | CMP_STOP : 0;
+       return 0;
 }
 
 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,
+               .obj = object,
+       };
 
        if (!object_type) {
                return -1;
        }
 
-       object_wizard = ao2_callback(object_type->wizards, 0, sorcery_wizard_create, object);
+       if ((object_wizard = ao2_callback(object_type->wizards, 0, sorcery_wizard_create, &sdetails)) &&
+               ao2_container_count(object_type->observers)) {
+               struct sorcery_observer_invocation *invocation = sorcery_observer_invocation_alloc(object_type, object);
+
+               if (invocation && ast_taskprocessor_push(object_type->serializer, sorcery_observers_notify_create, invocation)) {
+                       ao2_cleanup(invocation);
+               }
+       }
 
        return object_wizard ? 0 : -1;
 }
 
+/*! \brief Internal callback function which notifies an individual observer that an object has been updated */
+static int sorcery_observer_notify_update(void *obj, void *arg, int flags)
+{
+       const struct ast_sorcery_object_type_observer *observer = obj;
+
+       if (observer->callbacks->updated) {
+               observer->callbacks->updated(arg);
+       }
+
+       return 0;
+}
+
+/*! \brief Internal callback function which notifies observers that an object has been updated */
+static int sorcery_observers_notify_update(void *data)
+{
+       struct sorcery_observer_invocation *invocation = data;
+
+       ao2_callback(invocation->object_type->observers, OBJ_NODATA, sorcery_observer_notify_update, invocation->object);
+       ao2_cleanup(invocation);
+
+       return 0;
+}
+
 /*! \brief Internal function which returns if a wizard has updated the object */
 static int sorcery_wizard_update(void *obj, void *arg, int flags)
 {
        const struct ast_sorcery_object_wizard *object_wizard = obj;
+       const struct sorcery_details *details = arg;
 
-       return (object_wizard->wizard->update && !object_wizard->wizard->update(object_wizard->data, arg) &&
+       return (object_wizard->wizard->update && !object_wizard->wizard->update(details->sorcery, object_wizard->data, details->obj) &&
                !object_wizard->caching) ? CMP_MATCH | CMP_STOP : 0;
 }
 
 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,
+               .obj = object,
+       };
 
        if (!object_type) {
                return -1;
        }
 
-       object_wizard = ao2_callback(object_type->wizards, 0, sorcery_wizard_update, object);
+       if ((object_wizard = ao2_callback(object_type->wizards, 0, sorcery_wizard_update, &sdetails)) &&
+               ao2_container_count(object_type->observers)) {
+               struct sorcery_observer_invocation *invocation = sorcery_observer_invocation_alloc(object_type, object);
+
+               if (invocation && ast_taskprocessor_push(object_type->serializer, sorcery_observers_notify_update, invocation)) {
+                       ao2_cleanup(invocation);
+               }
+       }
 
        return object_wizard ? 0 : -1;
 }
 
+/*! \brief Internal callback function which notifies an individual observer that an object has been deleted */
+static int sorcery_observer_notify_delete(void *obj, void *arg, int flags)
+{
+       const struct ast_sorcery_object_type_observer *observer = obj;
+
+       if (observer->callbacks->deleted) {
+               observer->callbacks->deleted(arg);
+       }
+
+       return 0;
+}
+
+/*! \brief Internal callback function which notifies observers that an object has been deleted */
+static int sorcery_observers_notify_delete(void *data)
+{
+       struct sorcery_observer_invocation *invocation = data;
+
+       ao2_callback(invocation->object_type->observers, OBJ_NODATA, sorcery_observer_notify_delete, invocation->object);
+       ao2_cleanup(invocation);
+
+       return 0;
+}
+
 /*! \brief Internal function which returns if a wizard has deleted the object */
 static int sorcery_wizard_delete(void *obj, void *arg, int flags)
 {
        const struct ast_sorcery_object_wizard *object_wizard = obj;
+       const struct sorcery_details *details = arg;
 
-       return (object_wizard->wizard->delete && !object_wizard->wizard->delete(object_wizard->data, arg) &&
+       return (object_wizard->wizard->delete && !object_wizard->wizard->delete(details->sorcery, object_wizard->data, details->obj) &&
                !object_wizard->caching) ? CMP_MATCH | CMP_STOP : 0;
 }
 
 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,
+               .obj = object,
+       };
 
        if (!object_type) {
                return -1;
        }
 
-       object_wizard = ao2_callback(object_type->wizards, 0, sorcery_wizard_delete, object);
+       if ((object_wizard = ao2_callback(object_type->wizards, 0, sorcery_wizard_delete, &sdetails)) &&
+               ao2_container_count(object_type->observers)) {
+               struct sorcery_observer_invocation *invocation = sorcery_observer_invocation_alloc(object_type, object);
+
+               if (invocation && ast_taskprocessor_push(object_type->serializer, sorcery_observers_notify_delete, invocation)) {
+                       ao2_cleanup(invocation);
+               }
+       }
 
        return object_wizard ? 0 : -1;
 }
@@ -1037,11 +1455,92 @@ void ast_sorcery_unref(struct ast_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)
+{
+       RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, type, OBJ_KEY), ao2_cleanup);
+       struct ast_sorcery_object_type_observer *observer;
+
+       if (!object_type || !callbacks) {
+               return -1;
+       }
+
+       if (!(observer = ao2_alloc(sizeof(*observer), NULL))) {
+               return -1;
+       }
+
+       observer->callbacks = callbacks;
+       ao2_link(object_type->observers, observer);
+       ao2_ref(observer, -1);
+
+       return 0;
+}
+
+/*! \brief Internal callback function for removing an observer */
+static int sorcery_observer_remove(void *obj, void *arg, int flags)
+{
+       const struct ast_sorcery_object_type_observer *observer = obj;
+
+       return (observer->callbacks == arg) ? CMP_MATCH | CMP_STOP : 0;
+}
+
+void ast_sorcery_observer_remove(const struct ast_sorcery *sorcery, const char *type, struct ast_sorcery_observer *callbacks)
+{
+       RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, type, OBJ_KEY), ao2_cleanup);
+
+       if (!object_type) {
+               return;
+       }
+
+       ao2_callback(object_type->observers, OBJ_NODATA | OBJ_UNLINK, sorcery_observer_remove, callbacks);
 }