Container.vala 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. /**
  2. * Container - Container entity for child entities
  3. *
  4. * A Container is a folder-like container that can hold child entities
  5. * of any type (Container, Document, Category, or Index).
  6. *
  7. * @version 0.1
  8. * @since 0.1
  9. */
  10. namespace Implexus.Entities {
  11. /**
  12. * Container entity that can hold child entities.
  13. *
  14. * Containers are similar to filesystem folders - they can contain
  15. * any type of entity including other containers, documents, categories,
  16. * and indexes.
  17. *
  18. * Example usage:
  19. * {{{
  20. * var root = yield engine.get_root_async();
  21. * var users = yield root.create_container_async("users");
  22. * var john = yield users.create_document_async("john", "User");
  23. * yield john.set_entity_property_async("email", new Invercargill.NativeElement<string>("john@example.com"));
  24. * }}}
  25. */
  26. public class Container : AbstractEntity {
  27. // === Constructors ===
  28. /**
  29. * Creates a new Container with the given engine and path.
  30. *
  31. * @param engine The engine that manages this entity
  32. * @param path The path to this container
  33. */
  34. public Container(Core.Engine engine, Core.EntityPath path) {
  35. base(engine, path);
  36. }
  37. // === Entity Type ===
  38. /**
  39. * {@inheritDoc}
  40. */
  41. public override Core.EntityType entity_type {
  42. get { return Core.EntityType.CONTAINER; }
  43. }
  44. // === Child Navigation (Async) ===
  45. /**
  46. * {@inheritDoc}
  47. *
  48. * Returns the names of all children stored in this container.
  49. */
  50. public override async Invercargill.ReadOnlySet<string> get_child_names_async() throws Core.EntityError {
  51. var storage = _engine.configuration.storage;
  52. try {
  53. var children = storage.get_children(_path);
  54. var set = new Invercargill.DataStructures.HashSet<string>();
  55. foreach (var name in children) {
  56. set.add(name);
  57. }
  58. return set;
  59. } catch (Storage.StorageError e) {
  60. throw new Core.EntityError.STORAGE_ERROR("Failed to get children: %s".printf(e.message));
  61. }
  62. }
  63. /**
  64. * {@inheritDoc}
  65. *
  66. * Gets a child entity by name from storage.
  67. */
  68. public override async Core.Entity? get_child_async(string name) throws Core.EntityError {
  69. var child_path = _path.append_child(name);
  70. return yield _engine.get_entity_async(child_path);
  71. }
  72. /**
  73. * {@inheritDoc}
  74. *
  75. * Returns all child entities (eager loading).
  76. */
  77. public override async Core.Entity[] get_children_async() throws Core.EntityError {
  78. var names = yield get_child_names_async();
  79. var children = new Core.Entity[0];
  80. foreach (var name in names) {
  81. var child = yield get_child_async(name);
  82. if (child != null) {
  83. children += (!) child;
  84. }
  85. }
  86. return children;
  87. }
  88. // === Child Creation (Async) ===
  89. /**
  90. * {@inheritDoc}
  91. *
  92. * Creates a new Container child in this container.
  93. *
  94. * @param name The name for the new container
  95. * @return The created container entity
  96. * @throws Core.EntityError.INVALID_PATH if name is empty
  97. * @throws Core.EntityError.ENTITY_ALREADY_EXISTS if an entity with this name already exists
  98. */
  99. public override async Core.Entity? create_container_async(string name) throws Core.EntityError {
  100. validate_can_create_child(name);
  101. var child_path = _path.append_child(name);
  102. // Check if we're in a transaction
  103. var embedded_engine = _engine as Engine.EmbeddedEngine;
  104. var tx = embedded_engine != null ? embedded_engine.current_transaction : null;
  105. if (tx != null) {
  106. // Queue operations in transaction
  107. tx.record_create_entity(child_path, Core.EntityType.CONTAINER, null);
  108. tx.record_add_child(_path, name);
  109. } else {
  110. // Direct write when not in transaction
  111. var storage = _engine.configuration.storage;
  112. try {
  113. storage.store_entity_metadata(child_path, Core.EntityType.CONTAINER, null);
  114. storage.add_child(_path, name);
  115. } catch (Storage.StorageError e) {
  116. throw new Core.EntityError.STORAGE_ERROR("Failed to create container: %s".printf(e.message));
  117. }
  118. }
  119. // Create entity instance and notify engine
  120. var container = new Container(_engine, child_path);
  121. _engine.entity_created(container);
  122. return container;
  123. }
  124. /**
  125. * {@inheritDoc}
  126. *
  127. * Creates a new Document child in this container.
  128. *
  129. * @param name The name for the new document
  130. * @param type_label The application-defined type for the document
  131. * @return The created document entity
  132. * @throws Core.EntityError.INVALID_PATH if name is empty
  133. * @throws Core.EntityError.ENTITY_ALREADY_EXISTS if an entity with this name already exists
  134. */
  135. public override async Core.Entity? create_document_async(string name, string type_label) throws Core.EntityError {
  136. validate_can_create_child(name);
  137. var child_path = _path.append_child(name);
  138. // Check if we're in a transaction
  139. var embedded_engine = _engine as Engine.EmbeddedEngine;
  140. var tx = embedded_engine != null ? embedded_engine.current_transaction : null;
  141. if (tx != null) {
  142. // Queue operations in transaction
  143. tx.record_create_entity(child_path, Core.EntityType.DOCUMENT, type_label);
  144. tx.record_add_child(_path, name);
  145. // Note: CREATE_ENTITY for DOCUMENT already stores empty properties
  146. } else {
  147. // Direct write when not in transaction
  148. var storage = _engine.configuration.storage;
  149. try {
  150. storage.store_entity_metadata(child_path, Core.EntityType.DOCUMENT, type_label);
  151. storage.store_properties(child_path, new Invercargill.DataStructures.PropertyDictionary());
  152. storage.add_child(_path, name);
  153. // Register document in type index for Index.populate_index()
  154. if (embedded_engine != null) {
  155. ((!) embedded_engine).entity_store.register_document_type(type_label, child_path.to_string());
  156. }
  157. } catch (Storage.StorageError e) {
  158. throw new Core.EntityError.STORAGE_ERROR("Failed to create document: %s".printf(e.message));
  159. }
  160. }
  161. // Create entity instance and notify engine
  162. var document = new Document(_engine, child_path, type_label);
  163. _engine.entity_created(document);
  164. return document;
  165. }
  166. /**
  167. * {@inheritDoc}
  168. *
  169. * Creates a new Category child in this container.
  170. *
  171. * The category will be populated with all existing documents that match
  172. * the predicate expression, and will automatically register for change
  173. * notifications to keep its index up to date.
  174. *
  175. * @param name The name for the new category
  176. * @param type_label The document type to filter
  177. * @param expression The boolean predicate expression for filtering
  178. * @return The created category entity
  179. * @throws Core.EntityError.INVALID_PATH if name is empty
  180. * @throws Core.EntityError.ENTITY_ALREADY_EXISTS if an entity with this name already exists
  181. */
  182. public override async Core.Entity? create_category_async(
  183. string name,
  184. string type_label,
  185. string expression
  186. ) throws Core.EntityError {
  187. validate_can_create_child(name);
  188. var child_path = _path.append_child(name);
  189. // Check if we're in a transaction
  190. var embedded_engine = _engine as Engine.EmbeddedEngine;
  191. var tx = embedded_engine != null ? embedded_engine.current_transaction : null;
  192. if (tx != null) {
  193. // Queue operations in transaction
  194. tx.record_create_entity(child_path, Core.EntityType.CATEGORY, type_label);
  195. tx.record_save_category_config(child_path, type_label, expression);
  196. tx.record_add_child(_path, name);
  197. } else {
  198. // Direct write when not in transaction
  199. var storage = _engine.configuration.storage;
  200. try {
  201. storage.store_entity_metadata(child_path, Core.EntityType.CATEGORY, type_label);
  202. storage.store_category_config(child_path, type_label, expression);
  203. storage.add_child(_path, name);
  204. } catch (Storage.StorageError e) {
  205. throw new Core.EntityError.STORAGE_ERROR("Failed to create category: %s".printf(e.message));
  206. }
  207. }
  208. // Create entity instance
  209. var category = new Category(_engine, child_path, type_label, expression);
  210. // Populate the index with existing documents
  211. try {
  212. category.populate_index();
  213. } catch (Core.EngineError e) {
  214. warning("Failed to populate category index: %s", e.message);
  215. }
  216. // Register with hook manager for change notifications
  217. category.register_hooks();
  218. // Notify engine of creation
  219. _engine.entity_created(category);
  220. return category;
  221. }
  222. /**
  223. * {@inheritDoc}
  224. *
  225. * Creates a new Catalogue child in this container.
  226. *
  227. * The catalogue will be populated with all existing documents grouped
  228. * by the key extracted from the expression, and will automatically
  229. * register for change notifications to keep its index up to date.
  230. *
  231. * @param name The name for the new catalogue
  232. * @param type_label The document type to catalogue
  233. * @param expression The expression to extract the grouping key
  234. * @return The created catalogue entity
  235. * @throws Core.EntityError.INVALID_PATH if name is empty
  236. * @throws Core.EntityError.ENTITY_ALREADY_EXISTS if an entity with this name already exists
  237. */
  238. public override async Core.Entity? create_catalogue_async(
  239. string name,
  240. string type_label,
  241. string expression
  242. ) throws Core.EntityError {
  243. validate_can_create_child(name);
  244. var child_path = _path.append_child(name);
  245. // Check if we're in a transaction
  246. var embedded_engine = _engine as Engine.EmbeddedEngine;
  247. var tx = embedded_engine != null ? embedded_engine.current_transaction : null;
  248. if (tx != null) {
  249. // Queue operations in transaction
  250. tx.record_create_entity(child_path, Core.EntityType.CATALOGUE, type_label);
  251. tx.record_save_catalogue_config(child_path, type_label, expression);
  252. tx.record_add_child(_path, name);
  253. } else {
  254. // Direct write when not in transaction
  255. var storage = _engine.configuration.storage;
  256. try {
  257. storage.store_entity_metadata(child_path, Core.EntityType.CATALOGUE, type_label);
  258. storage.store_catalogue_config(child_path, type_label, expression);
  259. storage.add_child(_path, name);
  260. } catch (Storage.StorageError e) {
  261. throw new Core.EntityError.STORAGE_ERROR("Failed to create catalogue: %s".printf(e.message));
  262. }
  263. }
  264. // Create entity instance
  265. var catalogue = new Catalogue(_engine, child_path, type_label, expression);
  266. // Populate the index with existing documents
  267. try {
  268. catalogue.populate_index();
  269. } catch (Core.EngineError e) {
  270. warning("Failed to populate catalogue index: %s", e.message);
  271. }
  272. // Register with hook manager for change notifications
  273. catalogue.register_hooks();
  274. // Notify engine of creation
  275. _engine.entity_created(catalogue);
  276. return catalogue;
  277. }
  278. /**
  279. * {@inheritDoc}
  280. *
  281. * Creates a new Index child in this container.
  282. *
  283. * The index will be populated with n-gram indices for all existing
  284. * documents of the configured type, and will automatically register
  285. * for change notifications to keep its index up to date.
  286. *
  287. * @param name The name for the new index
  288. * @param type_label The document type to index
  289. * @param expression The expression/property to index for text search
  290. * @return The created index entity
  291. * @throws Core.EntityError.INVALID_PATH if name is empty
  292. * @throws Core.EntityError.ENTITY_ALREADY_EXISTS if an entity with this name already exists
  293. */
  294. public override async Core.Entity? create_index_async(
  295. string name,
  296. string type_label,
  297. string expression
  298. ) throws Core.EntityError {
  299. validate_can_create_child(name);
  300. var child_path = _path.append_child(name);
  301. // Check if we're in a transaction
  302. var embedded_engine = _engine as Engine.EmbeddedEngine;
  303. var tx = embedded_engine != null ? embedded_engine.current_transaction : null;
  304. if (tx != null) {
  305. // Queue operations in transaction
  306. tx.record_create_entity(child_path, Core.EntityType.INDEX, type_label);
  307. tx.record_save_category_config(child_path, type_label, expression);
  308. tx.record_add_child(_path, name);
  309. } else {
  310. // Direct write when not in transaction
  311. var storage = _engine.configuration.storage;
  312. try {
  313. storage.store_entity_metadata(child_path, Core.EntityType.INDEX, type_label);
  314. storage.store_category_config(child_path, type_label, expression);
  315. storage.add_child(_path, name);
  316. } catch (Storage.StorageError e) {
  317. throw new Core.EntityError.STORAGE_ERROR("Failed to create index: %s".printf(e.message));
  318. }
  319. }
  320. // Create entity instance
  321. var index = new Index(_engine, child_path, type_label, expression);
  322. // Populate the n-gram index with existing documents
  323. try {
  324. index.populate_index();
  325. } catch (Core.EngineError e) {
  326. warning("Failed to populate index: %s", e.message);
  327. }
  328. // Register with hook manager for change notifications
  329. index.register_hooks();
  330. // Notify engine of creation
  331. _engine.entity_created(index);
  332. return index;
  333. }
  334. // === Child Deletion (Async) ===
  335. /**
  336. * Deletes a child entity from this container.
  337. *
  338. * @param name The name of the child to delete
  339. * @throws Core.EntityError if deletion fails
  340. */
  341. public async void delete_child_async(string name) throws Core.EntityError {
  342. var child = yield get_child_async(name);
  343. if (child == null) {
  344. throw new Core.EntityError.ENTITY_NOT_FOUND(
  345. "Child not found: %s".printf(name)
  346. );
  347. }
  348. yield ((!) child).delete_async();
  349. }
  350. // === Lifecycle (Async) ===
  351. /**
  352. * {@inheritDoc}
  353. *
  354. * Deletes this container and all its children recursively.
  355. */
  356. public override async void delete_async() throws Core.EntityError {
  357. // Delete all children first (recursively)
  358. var names = yield get_child_names_async();
  359. foreach (var child_name in names) {
  360. var child = yield get_child_async(child_name);
  361. if (child != null) {
  362. try {
  363. yield ((!) child).delete_async();
  364. } catch (Core.EntityError e) {
  365. warning("Failed to delete child %s: %s", child_name, e.message);
  366. }
  367. }
  368. }
  369. // Then delete this container
  370. yield base.delete_async();
  371. }
  372. // === Validation ===
  373. /**
  374. * Validates that a child with the given name can be created.
  375. *
  376. * @param name The name to validate
  377. * @throws Core.EntityError.INVALID_PATH if name is empty
  378. * @throws Core.EntityError.ENTITY_ALREADY_EXISTS if an entity already exists
  379. */
  380. private void validate_can_create_child(string name) throws Core.EntityError {
  381. if (name == null || name == "") {
  382. throw new Core.EntityError.INVALID_PATH("Child name cannot be empty");
  383. }
  384. var child_path = _path.append_child(name);
  385. var embedded = _engine as Engine.EmbeddedEngine;
  386. if (embedded != null && ((!) embedded).entity_exists_sync(child_path)) {
  387. throw new Core.EntityError.ENTITY_ALREADY_EXISTS(
  388. "Entity already exists: %s".printf(child_path.to_string())
  389. );
  390. }
  391. }
  392. }
  393. } // namespace Implexus.Entities