| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452 |
- /**
- * Container - Container entity for child entities
- *
- * A Container is a folder-like container that can hold child entities
- * of any type (Container, Document, Category, or Index).
- *
- * @version 0.1
- * @since 0.1
- */
- namespace Implexus.Entities {
- /**
- * Container entity that can hold child entities.
- *
- * Containers are similar to filesystem folders - they can contain
- * any type of entity including other containers, documents, categories,
- * and indexes.
- *
- * Example usage:
- * {{{
- * var root = yield engine.get_root_async();
- * var users = yield root.create_container_async("users");
- * var john = yield users.create_document_async("john", "User");
- * yield john.set_entity_property_async("email", new Invercargill.NativeElement<string>("john@example.com"));
- * }}}
- */
- public class Container : AbstractEntity {
-
- // === Constructors ===
-
- /**
- * Creates a new Container with the given engine and path.
- *
- * @param engine The engine that manages this entity
- * @param path The path to this container
- */
- public Container(Core.Engine engine, Core.EntityPath path) {
- base(engine, path);
- }
-
- // === Entity Type ===
-
- /**
- * {@inheritDoc}
- */
- public override Core.EntityType entity_type {
- get { return Core.EntityType.CONTAINER; }
- }
-
- // === Child Navigation (Async) ===
-
- /**
- * {@inheritDoc}
- *
- * Returns the names of all children stored in this container.
- */
- public override async Invercargill.ReadOnlySet<string> get_child_names_async() throws Core.EntityError {
- var storage = _engine.configuration.storage;
- try {
- var children = storage.get_children(_path);
- var set = new Invercargill.DataStructures.HashSet<string>();
- foreach (var name in children) {
- set.add(name);
- }
- return set;
- } catch (Storage.StorageError e) {
- throw new Core.EntityError.STORAGE_ERROR("Failed to get children: %s".printf(e.message));
- }
- }
-
- /**
- * {@inheritDoc}
- *
- * Gets a child entity by name from storage.
- */
- public override async Core.Entity? get_child_async(string name) throws Core.EntityError {
- var child_path = _path.append_child(name);
- return yield _engine.get_entity_async(child_path);
- }
-
- /**
- * {@inheritDoc}
- *
- * Returns all child entities (eager loading).
- */
- public override async Core.Entity[] get_children_async() throws Core.EntityError {
- var names = yield get_child_names_async();
- var children = new Core.Entity[0];
- foreach (var name in names) {
- var child = yield get_child_async(name);
- if (child != null) {
- children += (!) child;
- }
- }
- return children;
- }
-
- // === Child Creation (Async) ===
-
- /**
- * {@inheritDoc}
- *
- * Creates a new Container child in this container.
- *
- * @param name The name for the new container
- * @return The created container entity
- * @throws Core.EntityError.INVALID_PATH if name is empty
- * @throws Core.EntityError.ENTITY_ALREADY_EXISTS if an entity with this name already exists
- */
- public override async Core.Entity? create_container_async(string name) throws Core.EntityError {
- validate_can_create_child(name);
-
- var child_path = _path.append_child(name);
-
- // Check if we're in a transaction
- var embedded_engine = _engine as Engine.EmbeddedEngine;
- var tx = embedded_engine != null ? embedded_engine.current_transaction : null;
-
- if (tx != null) {
- // Queue operations in transaction
- tx.record_create_entity(child_path, Core.EntityType.CONTAINER, null);
- tx.record_add_child(_path, name);
- } else {
- // Direct write when not in transaction
- var storage = _engine.configuration.storage;
- try {
- storage.store_entity_metadata(child_path, Core.EntityType.CONTAINER, null);
- storage.add_child(_path, name);
- } catch (Storage.StorageError e) {
- throw new Core.EntityError.STORAGE_ERROR("Failed to create container: %s".printf(e.message));
- }
- }
-
- // Create entity instance and notify engine
- var container = new Container(_engine, child_path);
- _engine.entity_created(container);
-
- return container;
- }
-
- /**
- * {@inheritDoc}
- *
- * Creates a new Document child in this container.
- *
- * @param name The name for the new document
- * @param type_label The application-defined type for the document
- * @return The created document entity
- * @throws Core.EntityError.INVALID_PATH if name is empty
- * @throws Core.EntityError.ENTITY_ALREADY_EXISTS if an entity with this name already exists
- */
- public override async Core.Entity? create_document_async(string name, string type_label) throws Core.EntityError {
- validate_can_create_child(name);
-
- var child_path = _path.append_child(name);
-
- // Check if we're in a transaction
- var embedded_engine = _engine as Engine.EmbeddedEngine;
- var tx = embedded_engine != null ? embedded_engine.current_transaction : null;
-
- if (tx != null) {
- // Queue operations in transaction
- tx.record_create_entity(child_path, Core.EntityType.DOCUMENT, type_label);
- tx.record_add_child(_path, name);
- // Note: CREATE_ENTITY for DOCUMENT already stores empty properties
- } else {
- // Direct write when not in transaction
- var storage = _engine.configuration.storage;
- try {
- storage.store_entity_metadata(child_path, Core.EntityType.DOCUMENT, type_label);
- storage.store_properties(child_path, new Invercargill.DataStructures.PropertyDictionary());
- storage.add_child(_path, name);
-
- // Register document in type index for Index.populate_index()
- if (embedded_engine != null) {
- ((!) embedded_engine).entity_store.register_document_type(type_label, child_path.to_string());
- }
- } catch (Storage.StorageError e) {
- throw new Core.EntityError.STORAGE_ERROR("Failed to create document: %s".printf(e.message));
- }
- }
-
- // Create entity instance and notify engine
- var document = new Document(_engine, child_path, type_label);
- _engine.entity_created(document);
-
- return document;
- }
-
- /**
- * {@inheritDoc}
- *
- * Creates a new Category child in this container.
- *
- * The category will be populated with all existing documents that match
- * the predicate expression, and will automatically register for change
- * notifications to keep its index up to date.
- *
- * @param name The name for the new category
- * @param type_label The document type to filter
- * @param expression The boolean predicate expression for filtering
- * @return The created category entity
- * @throws Core.EntityError.INVALID_PATH if name is empty
- * @throws Core.EntityError.ENTITY_ALREADY_EXISTS if an entity with this name already exists
- */
- public override async Core.Entity? create_category_async(
- string name,
- string type_label,
- string expression
- ) throws Core.EntityError {
- validate_can_create_child(name);
-
- var child_path = _path.append_child(name);
-
- // Check if we're in a transaction
- var embedded_engine = _engine as Engine.EmbeddedEngine;
- var tx = embedded_engine != null ? embedded_engine.current_transaction : null;
-
- if (tx != null) {
- // Queue operations in transaction
- tx.record_create_entity(child_path, Core.EntityType.CATEGORY, type_label);
- tx.record_save_category_config(child_path, type_label, expression);
- tx.record_add_child(_path, name);
- } else {
- // Direct write when not in transaction
- var storage = _engine.configuration.storage;
- try {
- storage.store_entity_metadata(child_path, Core.EntityType.CATEGORY, type_label);
- storage.store_category_config(child_path, type_label, expression);
- storage.add_child(_path, name);
- } catch (Storage.StorageError e) {
- throw new Core.EntityError.STORAGE_ERROR("Failed to create category: %s".printf(e.message));
- }
- }
-
- // Create entity instance
- var category = new Category(_engine, child_path, type_label, expression);
-
- // Populate the index with existing documents
- try {
- category.populate_index();
- } catch (Core.EngineError e) {
- warning("Failed to populate category index: %s", e.message);
- }
-
- // Register with hook manager for change notifications
- category.register_hooks();
-
- // Notify engine of creation
- _engine.entity_created(category);
-
- return category;
- }
-
- /**
- * {@inheritDoc}
- *
- * Creates a new Catalogue child in this container.
- *
- * The catalogue will be populated with all existing documents grouped
- * by the key extracted from the expression, and will automatically
- * register for change notifications to keep its index up to date.
- *
- * @param name The name for the new catalogue
- * @param type_label The document type to catalogue
- * @param expression The expression to extract the grouping key
- * @return The created catalogue entity
- * @throws Core.EntityError.INVALID_PATH if name is empty
- * @throws Core.EntityError.ENTITY_ALREADY_EXISTS if an entity with this name already exists
- */
- public override async Core.Entity? create_catalogue_async(
- string name,
- string type_label,
- string expression
- ) throws Core.EntityError {
- validate_can_create_child(name);
-
- var child_path = _path.append_child(name);
-
- // Check if we're in a transaction
- var embedded_engine = _engine as Engine.EmbeddedEngine;
- var tx = embedded_engine != null ? embedded_engine.current_transaction : null;
-
- if (tx != null) {
- // Queue operations in transaction
- tx.record_create_entity(child_path, Core.EntityType.CATALOGUE, type_label);
- tx.record_save_catalogue_config(child_path, type_label, expression);
- tx.record_add_child(_path, name);
- } else {
- // Direct write when not in transaction
- var storage = _engine.configuration.storage;
- try {
- storage.store_entity_metadata(child_path, Core.EntityType.CATALOGUE, type_label);
- storage.store_catalogue_config(child_path, type_label, expression);
- storage.add_child(_path, name);
- } catch (Storage.StorageError e) {
- throw new Core.EntityError.STORAGE_ERROR("Failed to create catalogue: %s".printf(e.message));
- }
- }
-
- // Create entity instance
- var catalogue = new Catalogue(_engine, child_path, type_label, expression);
-
- // Populate the index with existing documents
- try {
- catalogue.populate_index();
- } catch (Core.EngineError e) {
- warning("Failed to populate catalogue index: %s", e.message);
- }
-
- // Register with hook manager for change notifications
- catalogue.register_hooks();
-
- // Notify engine of creation
- _engine.entity_created(catalogue);
-
- return catalogue;
- }
-
- /**
- * {@inheritDoc}
- *
- * Creates a new Index child in this container.
- *
- * The index will be populated with n-gram indices for all existing
- * documents of the configured type, and will automatically register
- * for change notifications to keep its index up to date.
- *
- * @param name The name for the new index
- * @param type_label The document type to index
- * @param expression The expression/property to index for text search
- * @return The created index entity
- * @throws Core.EntityError.INVALID_PATH if name is empty
- * @throws Core.EntityError.ENTITY_ALREADY_EXISTS if an entity with this name already exists
- */
- public override async Core.Entity? create_index_async(
- string name,
- string type_label,
- string expression
- ) throws Core.EntityError {
- validate_can_create_child(name);
-
- var child_path = _path.append_child(name);
-
- // Check if we're in a transaction
- var embedded_engine = _engine as Engine.EmbeddedEngine;
- var tx = embedded_engine != null ? embedded_engine.current_transaction : null;
-
- if (tx != null) {
- // Queue operations in transaction
- tx.record_create_entity(child_path, Core.EntityType.INDEX, type_label);
- tx.record_save_category_config(child_path, type_label, expression);
- tx.record_add_child(_path, name);
- } else {
- // Direct write when not in transaction
- var storage = _engine.configuration.storage;
- try {
- storage.store_entity_metadata(child_path, Core.EntityType.INDEX, type_label);
- storage.store_category_config(child_path, type_label, expression);
- storage.add_child(_path, name);
- } catch (Storage.StorageError e) {
- throw new Core.EntityError.STORAGE_ERROR("Failed to create index: %s".printf(e.message));
- }
- }
-
- // Create entity instance
- var index = new Index(_engine, child_path, type_label, expression);
-
- // Populate the n-gram index with existing documents
- try {
- index.populate_index();
- } catch (Core.EngineError e) {
- warning("Failed to populate index: %s", e.message);
- }
-
- // Register with hook manager for change notifications
- index.register_hooks();
-
- // Notify engine of creation
- _engine.entity_created(index);
-
- return index;
- }
-
- // === Child Deletion (Async) ===
-
- /**
- * Deletes a child entity from this container.
- *
- * @param name The name of the child to delete
- * @throws Core.EntityError if deletion fails
- */
- public async void delete_child_async(string name) throws Core.EntityError {
- var child = yield get_child_async(name);
- if (child == null) {
- throw new Core.EntityError.ENTITY_NOT_FOUND(
- "Child not found: %s".printf(name)
- );
- }
- yield ((!) child).delete_async();
- }
-
- // === Lifecycle (Async) ===
-
- /**
- * {@inheritDoc}
- *
- * Deletes this container and all its children recursively.
- */
- public override async void delete_async() throws Core.EntityError {
- // Delete all children first (recursively)
- var names = yield get_child_names_async();
- foreach (var child_name in names) {
- var child = yield get_child_async(child_name);
- if (child != null) {
- try {
- yield ((!) child).delete_async();
- } catch (Core.EntityError e) {
- warning("Failed to delete child %s: %s", child_name, e.message);
- }
- }
- }
-
- // Then delete this container
- yield base.delete_async();
- }
-
- // === Validation ===
-
- /**
- * Validates that a child with the given name can be created.
- *
- * @param name The name to validate
- * @throws Core.EntityError.INVALID_PATH if name is empty
- * @throws Core.EntityError.ENTITY_ALREADY_EXISTS if an entity already exists
- */
- private void validate_can_create_child(string name) throws Core.EntityError {
- if (name == null || name == "") {
- throw new Core.EntityError.INVALID_PATH("Child name cannot be empty");
- }
-
- var child_path = _path.append_child(name);
- var embedded = _engine as Engine.EmbeddedEngine;
- if (embedded != null && ((!) embedded).entity_exists_sync(child_path)) {
- throw new Core.EntityError.ENTITY_ALREADY_EXISTS(
- "Entity already exists: %s".printf(child_path.to_string())
- );
- }
- }
- }
- } // namespace Implexus.Entities
|