Skip to main content

camel_core/
context.rs

1use std::any::{Any, TypeId};
2use std::collections::HashMap;
3use std::sync::Arc;
4use tokio_util::sync::CancellationToken;
5use tracing::{debug, trace};
6
7#[cfg(test)]
8use camel_api::StepLifecycle;
9use camel_api::component_metadata::ComponentMetadata;
10use camel_api::error_handler::ErrorHandlerConfig;
11use camel_api::{
12    CamelError, FunctionInvoker, HealthReport, Lifecycle, MetricsCollector, PlatformIdentity,
13    PlatformService, ReadinessGate, RouteTemplateSpec, RuntimeCommandBus, RuntimeQueryBus,
14    TemplateInstanceRecord,
15};
16use camel_component_api::{Component, ComponentContext, ComponentRegistrar};
17use camel_language_api::Language;
18
19use crate::health_registry::HealthCheckRegistry;
20use crate::language_registry::LanguageRegistryError;
21use crate::lifecycle::adapters::controller_actor::RouteControllerHandle;
22use crate::lifecycle::adapters::route_controller::SharedLanguageRegistry;
23use crate::lifecycle::application::route_definition::RouteDefinition;
24use crate::lifecycle::application::runtime_bus::RuntimeBus;
25use crate::registry::RegistryError;
26use crate::shared::components::domain::Registry;
27use crate::shared::observability::domain::TracerConfig;
28use crate::startup_validation::ConfigCheck;
29use crate::template::TemplateRegistry;
30
31pub use crate::context_builder::CamelContextBuilder;
32
33/// The CamelContext is the runtime engine that manages components, routes, and their lifecycle.
34///
35/// # Lifecycle
36///
37/// Call [`start()`](Self::start) to launch routes, then [`stop()`](Self::stop)
38/// or [`abort()`](Self::abort) to shut down. A stopped context can be restarted
39/// by calling `start()` again — the controller actor stays alive across stop/start
40/// cycles; only [`abort()`](Self::abort) is destructive.
41pub struct CamelContext {
42    registry: Arc<std::sync::Mutex<Registry>>,
43    route_controller: RouteControllerHandle,
44    actor_join: Option<tokio::task::JoinHandle<()>>,
45    supervision_join: Option<tokio::task::JoinHandle<()>>,
46    runtime: Arc<RuntimeBus>,
47    cancel_token: CancellationToken,
48    metrics: Arc<dyn MetricsCollector>,
49    // Platform ports
50    platform_service: Arc<dyn PlatformService>,
51    languages: SharedLanguageRegistry,
52    shutdown_timeout: std::time::Duration,
53    services: Vec<Box<dyn Lifecycle>>,
54    health_registry: Arc<HealthCheckRegistry>,
55    component_configs: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
56    function_invoker: Option<Arc<dyn FunctionInvoker>>,
57    template_registry: Arc<TemplateRegistry>,
58    idempotent_repositories: crate::registry::SharedIdempotentRegistry,
59    claim_check_repositories: crate::registry::SharedClaimCheckRegistry,
60    /// Fail-closed startup validation registry (ADR-0033). Checks are drained
61    /// and executed synchronously at the head of [`start()`](Self::start) before
62    /// any route consumer is started.
63    startup_checks: Vec<Box<dyn ConfigCheck>>,
64}
65
66/// Parts bag used by [`CamelContextBuilder::build`] to construct a [`CamelContext`]
67/// without accessing private fields from a sibling module.
68pub(crate) struct FromParts {
69    pub(crate) registry: Arc<std::sync::Mutex<Registry>>,
70    pub(crate) route_controller: RouteControllerHandle,
71    pub(crate) _actor_join: tokio::task::JoinHandle<()>,
72    pub(crate) supervision_join: Option<tokio::task::JoinHandle<()>>,
73    pub(crate) runtime: Arc<RuntimeBus>,
74    pub(crate) cancel_token: CancellationToken,
75    pub(crate) metrics: Arc<dyn MetricsCollector>,
76    pub(crate) platform_service: Arc<dyn PlatformService>,
77    pub(crate) languages: SharedLanguageRegistry,
78    pub(crate) shutdown_timeout: std::time::Duration,
79    pub(crate) services: Vec<Box<dyn Lifecycle>>,
80    pub(crate) health_registry: Arc<HealthCheckRegistry>,
81    pub(crate) component_configs: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
82    pub(crate) function_invoker: Option<Arc<dyn FunctionInvoker>>,
83    pub(crate) template_registry: Arc<TemplateRegistry>,
84    pub(crate) idempotent_repositories: crate::registry::SharedIdempotentRegistry,
85    pub(crate) claim_check_repositories: crate::registry::SharedClaimCheckRegistry,
86    pub(crate) startup_checks: Vec<Box<dyn ConfigCheck>>,
87}
88
89impl CamelContext {
90    pub(crate) fn from_parts(parts: FromParts) -> Self {
91        Self {
92            registry: parts.registry,
93            route_controller: parts.route_controller,
94            actor_join: Some(parts._actor_join),
95            supervision_join: parts.supervision_join,
96            runtime: parts.runtime,
97            cancel_token: parts.cancel_token,
98            metrics: parts.metrics,
99            platform_service: parts.platform_service,
100            languages: parts.languages,
101            shutdown_timeout: parts.shutdown_timeout,
102            services: parts.services,
103            health_registry: parts.health_registry,
104            component_configs: parts.component_configs,
105            function_invoker: parts.function_invoker,
106            template_registry: parts.template_registry,
107            idempotent_repositories: parts.idempotent_repositories,
108            claim_check_repositories: parts.claim_check_repositories,
109            startup_checks: parts.startup_checks,
110        }
111    }
112}
113
114/// Opaque handle for runtime side-effect execution operations.
115///
116/// This intentionally does not expose direct lifecycle mutation APIs to callers.
117#[derive(Clone)]
118pub struct RuntimeExecutionHandle {
119    pub(crate) controller: RouteControllerHandle,
120    pub(crate) runtime: Arc<RuntimeBus>,
121    pub(crate) function_invoker: Option<Arc<dyn FunctionInvoker>>,
122    /// Lifecycle handles to inject into the compiled pipeline during
123    /// `apply_swap`.  Used in tests to simulate lifecycle-bearing routes
124    /// (e.g. resequencer).  Always `None` in production.
125    #[cfg(test)]
126    #[allow(clippy::type_complexity)]
127    pub(crate) test_lifecycle_inject: Arc<std::sync::Mutex<Option<Vec<Arc<dyn StepLifecycle>>>>>,
128}
129
130impl RuntimeExecutionHandle {
131    pub(crate) async fn add_route_definition(
132        &self,
133        definition: RouteDefinition,
134    ) -> Result<(), CamelError> {
135        use crate::lifecycle::application::ports::RouteRegistrationPort;
136        self.runtime
137            .register_route(definition)
138            .await
139            .map_err(Into::into)
140    }
141
142    /// Compile a route definition into a bare BoxProcessor (no lifecycle).
143    /// Kept for tests; hot-reload uses the lifecycle-preserving variant instead.
144    #[allow(dead_code)]
145    pub(crate) async fn compile_route_definition(
146        &self,
147        definition: RouteDefinition,
148    ) -> Result<camel_api::BoxProcessor, CamelError> {
149        self.controller.compile_route_definition(definition).await
150    }
151
152    #[allow(dead_code)] // kept for potential future hot-reload paths
153    pub(crate) async fn compile_route_definition_with_generation(
154        &self,
155        definition: RouteDefinition,
156        generation: u64,
157    ) -> Result<camel_api::BoxProcessor, CamelError> {
158        self.controller
159            .compile_route_definition_with_generation(definition, generation)
160            .await
161    }
162
163    pub(crate) async fn compile_route_definition_pipeline(
164        &self,
165        definition: RouteDefinition,
166        generation: u64,
167    ) -> Result<crate::lifecycle::domain::CompiledPipeline, CamelError> {
168        self.controller
169            .compile_route_definition_pipeline(definition, generation)
170            .await
171    }
172
173    /// Compile without function generation, returning full CompiledPipeline.
174    /// Oracle Fix 1: stateless hot-reload path preserves lifecycle handles.
175    pub(crate) async fn compile_route_definition_dry_pipeline(
176        &self,
177        definition: RouteDefinition,
178    ) -> Result<crate::lifecycle::domain::CompiledPipeline, CamelError> {
179        self.controller
180            .compile_route_definition_dry_pipeline(definition)
181            .await
182    }
183
184    pub(crate) async fn prepare_route_definition_with_generation(
185        &self,
186        definition: RouteDefinition,
187        generation: u64,
188    ) -> Result<crate::lifecycle::domain::route_compilation::PreparedRoute, CamelError> {
189        self.controller
190            .prepare_route_definition_with_generation(definition, generation)
191            .await
192    }
193
194    pub(crate) async fn insert_prepared_route(
195        &self,
196        prepared: crate::lifecycle::domain::route_compilation::PreparedRoute,
197    ) -> Result<(), CamelError> {
198        self.controller.insert_prepared_route(prepared).await
199    }
200
201    pub(crate) async fn discard_prepared_staging(&self, route_id: &str) -> Result<(), CamelError> {
202        self.controller.discard_prepared_staging(route_id).await
203    }
204
205    pub(crate) async fn remove_route_preserving_functions(
206        &self,
207        route_id: String,
208    ) -> Result<(), CamelError> {
209        self.controller
210            .remove_route_preserving_functions(route_id)
211            .await
212    }
213
214    pub(crate) async fn register_route_aggregate(
215        &self,
216        route_id: String,
217    ) -> Result<(), CamelError> {
218        self.runtime.register_aggregate_only(route_id).await
219    }
220
221    pub(crate) async fn swap_route_pipeline(
222        &self,
223        route_id: &str,
224        pipeline: camel_api::BoxProcessor,
225    ) -> Result<(), CamelError> {
226        self.controller.swap_pipeline(route_id, pipeline).await
227    }
228
229    /// Stop the route via the reload path (graceful lifecycle drain).
230    pub(crate) async fn stop_route_reload(&self, route_id: &str) -> Result<(), CamelError> {
231        self.controller.stop_route_reload(route_id).await
232    }
233
234    /// Start the route via the reload path (re-create consumer).
235    pub(crate) async fn start_route_reload(&self, route_id: &str) -> Result<(), CamelError> {
236        self.controller.start_route_reload(route_id).await
237    }
238
239    /// Raw pipeline swap — bypasses the lifecycle/aggregate rejection check.
240    /// Only safe after the route has been stopped (Restart path).
241    pub(crate) async fn swap_route_pipeline_raw(
242        &self,
243        route_id: &str,
244        pipeline: camel_api::BoxProcessor,
245        lifecycle: Vec<Arc<dyn camel_api::StepLifecycle>>,
246    ) -> Result<(), CamelError> {
247        self.controller
248            .swap_pipeline_raw(route_id, pipeline, lifecycle)
249            .await
250    }
251
252    pub(crate) async fn execute_runtime_command(
253        &self,
254        cmd: camel_api::RuntimeCommand,
255    ) -> Result<camel_api::RuntimeCommandResult, CamelError> {
256        self.runtime.execute(cmd).await
257    }
258
259    pub(crate) async fn runtime_route_status(
260        &self,
261        route_id: &str,
262    ) -> Result<Option<String>, CamelError> {
263        match self
264            .runtime
265            .ask(camel_api::RuntimeQuery::GetRouteStatus {
266                route_id: route_id.to_string(),
267            })
268            .await
269        {
270            Ok(camel_api::RuntimeQueryResult::RouteStatus { status, .. }) => Ok(Some(status)),
271            Ok(_) => Err(CamelError::RouteError(
272                "unexpected runtime query response for route status".to_string(),
273            )),
274            Err(CamelError::RouteError(msg)) if msg.contains("not found") => Ok(None),
275            Err(err) => Err(err),
276        }
277    }
278
279    pub(crate) async fn runtime_route_ids(&self) -> Result<Vec<String>, CamelError> {
280        match self.runtime.ask(camel_api::RuntimeQuery::ListRoutes).await {
281            Ok(camel_api::RuntimeQueryResult::Routes { route_ids }) => Ok(route_ids),
282            Ok(_) => Err(CamelError::RouteError(
283                "unexpected runtime query response for route listing".to_string(),
284            )),
285            Err(err) => Err(err),
286        }
287    }
288
289    pub(crate) async fn route_source_hash(&self, route_id: &str) -> Option<u64> {
290        self.controller.route_source_hash(route_id).await
291    }
292
293    pub(crate) async fn in_flight_count(&self, route_id: &str) -> Result<u64, CamelError> {
294        if !self.controller.route_exists(route_id).await? {
295            return Err(CamelError::RouteError(format!(
296                "Route '{}' not found",
297                route_id
298            )));
299        }
300        Ok(self
301            .controller
302            .in_flight_count(route_id)
303            .await?
304            .unwrap_or(0))
305    }
306
307    /// Check whether the running route has lifecycle-bearing steps.
308    pub(crate) async fn route_has_lifecycle(&self, route_id: &str) -> bool {
309        self.controller
310            .route_has_lifecycle(route_id)
311            .await
312            .unwrap_or(false)
313    }
314
315    pub(crate) fn function_invoker(&self) -> Option<Arc<dyn FunctionInvoker>> {
316        self.function_invoker.clone()
317    }
318
319    #[cfg(test)]
320    pub(crate) async fn force_start_route_for_test(
321        &self,
322        route_id: &str,
323    ) -> Result<(), CamelError> {
324        self.controller.start_route(route_id).await
325    }
326
327    pub async fn controller_route_count_for_test(&self) -> usize {
328        self.controller.route_count().await.unwrap_or(0)
329    }
330}
331
332#[async_trait::async_trait]
333impl crate::hot_reload::ports::ReloadExecutorPort for RuntimeExecutionHandle {
334    async fn add_route_definition(&self, definition: RouteDefinition) -> Result<(), CamelError> {
335        RuntimeExecutionHandle::add_route_definition(self, definition).await
336    }
337
338    async fn compile_route_definition_pipeline(
339        &self,
340        definition: RouteDefinition,
341        generation: u64,
342    ) -> Result<crate::lifecycle::domain::CompiledPipeline, CamelError> {
343        RuntimeExecutionHandle::compile_route_definition_pipeline(self, definition, generation)
344            .await
345    }
346
347    async fn compile_route_definition_dry_pipeline(
348        &self,
349        definition: RouteDefinition,
350    ) -> Result<crate::lifecycle::domain::CompiledPipeline, CamelError> {
351        RuntimeExecutionHandle::compile_route_definition_dry_pipeline(self, definition).await
352    }
353
354    async fn prepare_route_definition_with_generation(
355        &self,
356        definition: RouteDefinition,
357        generation: u64,
358    ) -> Result<crate::lifecycle::domain::route_compilation::PreparedRoute, CamelError> {
359        RuntimeExecutionHandle::prepare_route_definition_with_generation(
360            self, definition, generation,
361        )
362        .await
363    }
364
365    async fn insert_prepared_route(
366        &self,
367        prepared: crate::lifecycle::domain::route_compilation::PreparedRoute,
368    ) -> Result<(), CamelError> {
369        RuntimeExecutionHandle::insert_prepared_route(self, prepared).await
370    }
371
372    async fn discard_prepared_staging(&self, route_id: &str) -> Result<(), CamelError> {
373        RuntimeExecutionHandle::discard_prepared_staging(self, route_id).await
374    }
375
376    async fn remove_route_preserving_functions(&self, route_id: String) -> Result<(), CamelError> {
377        RuntimeExecutionHandle::remove_route_preserving_functions(self, route_id).await
378    }
379
380    async fn register_route_aggregate(&self, route_id: String) -> Result<(), CamelError> {
381        RuntimeExecutionHandle::register_route_aggregate(self, route_id).await
382    }
383
384    async fn swap_route_pipeline(
385        &self,
386        route_id: &str,
387        pipeline: camel_api::BoxProcessor,
388    ) -> Result<(), CamelError> {
389        RuntimeExecutionHandle::swap_route_pipeline(self, route_id, pipeline).await
390    }
391
392    async fn stop_route_reload(&self, route_id: &str) -> Result<(), CamelError> {
393        RuntimeExecutionHandle::stop_route_reload(self, route_id).await
394    }
395
396    async fn start_route_reload(&self, route_id: &str) -> Result<(), CamelError> {
397        RuntimeExecutionHandle::start_route_reload(self, route_id).await
398    }
399
400    async fn swap_route_pipeline_raw(
401        &self,
402        route_id: &str,
403        pipeline: camel_api::BoxProcessor,
404        lifecycle: Vec<std::sync::Arc<dyn camel_api::StepLifecycle>>,
405    ) -> Result<(), CamelError> {
406        RuntimeExecutionHandle::swap_route_pipeline_raw(self, route_id, pipeline, lifecycle).await
407    }
408
409    async fn execute_runtime_command(
410        &self,
411        cmd: camel_api::RuntimeCommand,
412    ) -> Result<camel_api::RuntimeCommandResult, CamelError> {
413        RuntimeExecutionHandle::execute_runtime_command(self, cmd).await
414    }
415
416    async fn runtime_route_status(&self, route_id: &str) -> Result<Option<String>, CamelError> {
417        RuntimeExecutionHandle::runtime_route_status(self, route_id).await
418    }
419
420    async fn in_flight_count(&self, route_id: &str) -> Result<u64, CamelError> {
421        RuntimeExecutionHandle::in_flight_count(self, route_id).await
422    }
423
424    async fn route_has_lifecycle(&self, route_id: &str) -> bool {
425        RuntimeExecutionHandle::route_has_lifecycle(self, route_id).await
426    }
427
428    #[cfg(test)]
429    fn take_test_lifecycle_inject(
430        &self,
431    ) -> Option<Vec<std::sync::Arc<dyn camel_api::StepLifecycle>>> {
432        self.test_lifecycle_inject.lock().unwrap().take()
433    }
434}
435
436impl CamelContext {
437    pub fn builder() -> CamelContextBuilder {
438        CamelContextBuilder::new()
439    }
440
441    /// Set a global error handler applied to all routes without a per-route handler.
442    pub async fn set_error_handler(&mut self, config: ErrorHandlerConfig) {
443        let _ = self.route_controller.set_error_handler(config).await;
444    }
445
446    /// Enable or disable tracing globally.
447    pub async fn set_tracing(&mut self, enabled: bool) {
448        let _ = self
449            .route_controller
450            .set_tracer_config(TracerConfig {
451                enabled,
452                ..Default::default()
453            })
454            .await;
455    }
456
457    /// Configure tracing with full config.
458    pub async fn set_tracer_config(&mut self, config: TracerConfig) {
459        // Inject metrics collector if not already set
460        let config = if config.metrics_collector.is_none() {
461            TracerConfig {
462                metrics_collector: Some(Arc::clone(&self.metrics)),
463                ..config
464            }
465        } else {
466            config
467        };
468
469        let _ = self.route_controller.set_tracer_config(config).await;
470    }
471
472    /// Builder-style: enable tracing with default config.
473    pub async fn with_tracing(mut self) -> Self {
474        self.set_tracing(true).await;
475        self
476    }
477
478    /// Builder-style: configure tracing with custom config.
479    /// Note: tracing subscriber initialization (stdout/file output) is handled
480    /// separately via init_tracing_subscriber (called in camel-config bridge).
481    pub async fn with_tracer_config(mut self, config: TracerConfig) -> Self {
482        self.set_tracer_config(config).await;
483        self
484    }
485
486    /// Register a lifecycle service (Apache Camel: addService pattern)
487    ///
488    /// For services exposing `as_function_invoker()`, the invoker is propagated
489    /// to the route controller so that subsequent route definitions with function
490    /// steps work correctly.
491    ///
492    /// Prefer [`CamelContextBuilder::with_lifecycle`] when possible, which wires
493    /// the invoker at build time before any routes are added.
494    pub fn with_lifecycle<L: Lifecycle + 'static>(mut self, service: L) -> Self {
495        if let Some(collector) = service.as_metrics_collector() {
496            self.metrics = collector;
497        }
498        if let Some(invoker) = service.as_function_invoker() {
499            self.function_invoker = Some(invoker.clone());
500            if let Err(e) = self.route_controller.try_set_function_invoker(invoker) {
501                tracing::debug!("Failed to propagate function invoker to route controller: {e}");
502            }
503        }
504
505        self.services.push(Box::new(service));
506        self
507    }
508
509    /// Register a component with this context.
510    ///
511    /// Delegates to [`register_component_dyn`](Self::register_component_dyn)
512    /// so metadata harvesting happens regardless of which entry point
513    /// is used.
514    pub fn register_component<C: Component + 'static>(&mut self, component: C) {
515        self.register_component_dyn(Arc::new(component));
516    }
517
518    /// Register a startup `ConfigCheck` to be evaluated at the head of
519    /// [`start()`](Self::start). Established by ADR-0033.
520    ///
521    /// Checks are drained and executed synchronously before any route consumer
522    /// is started. If any check returns `Err`, `start()` fails closed with
523    /// `CamelError::Config(_)` and no route is started. The check list is
524    /// consumed (moved) during `start()` so this method may be called multiple
525    /// times to register an arbitrary number of checks.
526    pub fn add_startup_check(&mut self, check: Box<dyn ConfigCheck>) {
527        self.startup_checks.push(check);
528    }
529
530    /// Register a language with this context, keyed by name.
531    ///
532    /// Returns `Err(LanguageRegistryError::AlreadyRegistered)` if a language
533    /// with the same name is already registered. Use
534    /// [`resolve_language`](Self::resolve_language) to check before
535    /// registering, or choose a distinct name.
536    pub fn register_language(
537        &mut self,
538        name: impl Into<String>,
539        lang: Box<dyn Language>,
540    ) -> Result<(), LanguageRegistryError> {
541        let name = name.into();
542        let mut languages = self
543            .languages
544            .lock()
545            .expect("mutex poisoned: another thread panicked while holding this lock"); // allow-unwrap
546        if languages.contains_key(&name) {
547            return Err(LanguageRegistryError::AlreadyRegistered { name });
548        }
549        languages.insert(name, Arc::from(lang));
550        Ok(())
551    }
552
553    /// Resolve a language by name. Returns `None` if not registered.
554    pub fn resolve_language(&self, name: &str) -> Option<Arc<dyn Language>> {
555        let languages = self
556            .languages
557            .lock()
558            .expect("mutex poisoned: another thread panicked while holding this lock"); // allow-unwrap
559        languages.get(name).cloned()
560    }
561
562    /// Add a route definition to this context.
563    ///
564    /// The route must have an ID. Steps are resolved immediately using registered components.
565    pub async fn add_route_definition(
566        &self,
567        definition: RouteDefinition,
568    ) -> Result<(), CamelError> {
569        use crate::lifecycle::application::ports::RouteRegistrationPort;
570        debug!(
571            from = definition.from_uri(),
572            route_id = %definition.route_id(),
573            "Adding route definition"
574        );
575        self.runtime
576            .register_route(definition)
577            .await
578            .map_err(Into::into)
579    }
580
581    /// Access the component registry.
582    pub fn registry(&self) -> std::sync::MutexGuard<'_, Registry> {
583        self.registry
584            .lock()
585            .expect("mutex poisoned: another thread panicked while holding this lock") // allow-unwrap
586    }
587
588    /// Access the shared component registry Arc.
589    pub fn registry_arc(&self) -> Arc<std::sync::Mutex<Registry>> {
590        Arc::clone(&self.registry)
591    }
592
593    /// Get runtime execution handle for file-watcher integrations.
594    pub fn runtime_execution_handle(&self) -> RuntimeExecutionHandle {
595        RuntimeExecutionHandle {
596            controller: self.route_controller.clone(),
597            runtime: Arc::clone(&self.runtime),
598            function_invoker: self.function_invoker.clone(),
599            #[cfg(test)]
600            test_lifecycle_inject: Arc::new(std::sync::Mutex::new(None)),
601        }
602    }
603
604    /// Get the metrics collector.
605    pub fn metrics(&self) -> Arc<dyn MetricsCollector> {
606        Arc::clone(&self.metrics)
607    }
608
609    /// Get the platform service.
610    pub fn platform_service(&self) -> Arc<dyn PlatformService> {
611        Arc::clone(&self.platform_service)
612    }
613
614    /// Get the readiness gate port.
615    pub fn readiness_gate(&self) -> Arc<dyn ReadinessGate> {
616        self.platform_service.readiness_gate()
617    }
618
619    /// Get the platform identity.
620    pub fn platform_identity(&self) -> PlatformIdentity {
621        self.platform_service.identity()
622    }
623
624    /// Get the leadership service port.
625    pub fn leadership(&self) -> Arc<dyn camel_api::LeadershipService> {
626        self.platform_service.leadership()
627    }
628
629    /// Get runtime command/query bus handle.
630    pub fn runtime(&self) -> Arc<dyn camel_api::RuntimeHandle> {
631        self.runtime.clone()
632    }
633
634    /// Build a producer context wired to this runtime.
635    pub fn producer_context(&self) -> camel_api::ProducerContext {
636        camel_api::ProducerContext::new().with_runtime(self.runtime())
637    }
638
639    /// Query route status via runtime read-model.
640    pub async fn runtime_route_status(&self, route_id: &str) -> Result<Option<String>, CamelError> {
641        match self
642            .runtime()
643            .ask(camel_api::RuntimeQuery::GetRouteStatus {
644                route_id: route_id.to_string(),
645            })
646            .await
647        {
648            Ok(camel_api::RuntimeQueryResult::RouteStatus { status, .. }) => Ok(Some(status)),
649            Ok(_) => Err(CamelError::RouteError(
650                "unexpected runtime query response for route status".to_string(),
651            )),
652            Err(CamelError::RouteError(msg)) if msg.contains("not found") => Ok(None),
653            Err(err) => Err(err),
654        }
655    }
656
657    /// Start all routes. Each route's consumer will begin producing exchanges.
658    ///
659    /// Only routes with `auto_startup == true` will be started, in order of their
660    /// `startup_order` (lower values start first).
661    ///
662    /// Algorithm lives in `lifecycle::application::context_lifecycle::start_context`
663    /// (Tier C C2). Public signature is unchanged.
664    pub async fn start(&mut self) -> Result<(), CamelError> {
665        crate::lifecycle::application::context_lifecycle::start_context(
666            &mut self.services,
667            &mut self.startup_checks,
668            &self.runtime,
669            &self.route_controller,
670            &mut self.cancel_token,
671        )
672        .await
673    }
674
675    /// Graceful shutdown with default 30-second timeout.
676    pub async fn stop(&mut self) -> Result<(), CamelError> {
677        self.stop_timeout(self.shutdown_timeout).await
678    }
679
680    /// Graceful shutdown with custom timeout.
681    ///
682    /// Note: The timeout parameter is currently not propagated to the
683    /// RouteController's per-route shutdown timeout. The RouteController
684    /// uses a hardcoded 5-second default (`DEFAULT_SHUTDOWN_TIMEOUT`).
685    /// Full propagation is planned for a future version.
686    ///
687    /// Algorithm lives in `lifecycle::application::context_lifecycle::stop_context`
688    /// (Tier C C2). Public signature is unchanged.
689    pub async fn stop_timeout(&mut self, _timeout: std::time::Duration) -> Result<(), CamelError> {
690        crate::lifecycle::application::context_lifecycle::stop_context(
691            &self.cancel_token,
692            &mut self.supervision_join,
693            &self.runtime,
694            &self.route_controller,
695            &mut self.services,
696        )
697        .await
698    }
699
700    /// Get the graceful shutdown timeout used by [`stop()`](Self::stop).
701    pub fn shutdown_timeout(&self) -> std::time::Duration {
702        self.shutdown_timeout
703    }
704
705    /// Set the graceful shutdown timeout used by [`stop()`](Self::stop).
706    pub fn set_shutdown_timeout(&mut self, timeout: std::time::Duration) {
707        self.shutdown_timeout = timeout;
708    }
709
710    /// Test-only: take the actor join handle out of the context.
711    /// Used to verify the actor exits gracefully after stop().
712    #[cfg(test)]
713    pub(crate) fn take_actor_join(&mut self) -> Option<tokio::task::JoinHandle<()>> {
714        self.actor_join.take()
715    }
716
717    /// Immediate abort — kills all tasks without draining.
718    ///
719    /// Algorithm lives in `lifecycle::application::context_lifecycle::abort_context`
720    /// (Tier C C2). Public signature is unchanged. The use-case routes
721    /// through `RouteOrderingPort` + `RouteDestructiveTeardownPort`; the
722    /// same underlying `RouteControllerHandle` is passed twice as both
723    /// trait objects.
724    pub async fn abort(&mut self) {
725        crate::lifecycle::application::context_lifecycle::abort_context(
726            &self.cancel_token,
727            &mut self.supervision_join,
728            &self.runtime,
729            &self.route_controller as &dyn crate::lifecycle::application::ports::RouteOrderingPort,
730            &self.route_controller
731                as &dyn crate::lifecycle::application::ports::RouteDestructiveTeardownPort,
732            &mut self.services,
733            self.health_registry.cancel_token(),
734            &mut self.actor_join,
735        )
736        .await
737    }
738
739    /// Check health status of all registered services and lifecycle services.
740    pub async fn health_check(&self) -> HealthReport {
741        use camel_api::HealthSource;
742        self.health_report().await
743    }
744
745    pub fn health_registry(&self) -> Arc<HealthCheckRegistry> {
746        Arc::clone(&self.health_registry)
747    }
748
749    /// Store a component config. Overwrites any previously stored config of the same type.
750    pub fn set_component_config<T: 'static + Send + Sync>(&mut self, config: T) {
751        self.component_configs
752            .insert(TypeId::of::<T>(), Box::new(config));
753    }
754
755    /// Retrieve a stored component config by type. Returns None if not stored.
756    pub fn get_component_config<T: 'static + Send + Sync>(&self) -> Option<&T> {
757        self.component_configs
758            .get(&TypeId::of::<T>())
759            .and_then(|b| b.downcast_ref::<T>())
760    }
761
762    // --- Component Metadata ---
763
764    /// Get a component's harvested metadata by URI scheme.
765    pub fn component_metadata(&self, scheme: &str) -> Option<ComponentMetadata> {
766        self.registry.lock().ok()?.get_metadata(scheme)
767    }
768
769    /// Get metadata for every registered component.
770    pub fn all_component_metadata(&self) -> Vec<ComponentMetadata> {
771        self.registry
772            .lock()
773            .expect("mutex poisoned: another thread panicked while holding this lock") // allow-unwrap
774            .all_metadata()
775    }
776
777    /// Get a metadata catalog handle implementing
778    /// [`ComponentMetadataCatalog`](camel_api::component_metadata::ComponentMetadataCatalog).
779    ///
780    /// The returned handle shares the same `Arc<Mutex<Registry>>` as the
781    /// context, so registrations made through one are visible through the
782    /// other.
783    pub fn metadata_catalog(
784        &self,
785    ) -> crate::component_metadata_catalog::RuntimeComponentMetadataCatalog {
786        crate::component_metadata_catalog::RuntimeComponentMetadataCatalog::new(Arc::clone(
787            &self.registry,
788        ))
789    }
790
791    // --- Route Template Registry (data-only) ---
792
793    /// Register a route template specification.
794    ///
795    /// Returns `Err(CamelError)` if a template with the same ID is already registered.
796    pub fn add_route_template(&self, spec: RouteTemplateSpec) -> Result<(), CamelError> {
797        self.template_registry.register(spec)
798    }
799
800    /// Retrieve a route template specification by its ID.
801    pub fn get_route_template(&self, id: &str) -> Option<RouteTemplateSpec> {
802        self.template_registry.get(id)
803    }
804
805    /// Return all registered template IDs.
806    pub fn template_ids(&self) -> Vec<String> {
807        self.template_registry.template_ids()
808    }
809
810    /// Record a newly instantiated template instance.
811    pub fn record_template_instance(&self, record: TemplateInstanceRecord) {
812        self.template_registry.record_instance(record)
813    }
814
815    /// Return all instance records for a given template ID.
816    pub fn template_instances(&self, template_id: &str) -> Vec<TemplateInstanceRecord> {
817        self.template_registry.instances(template_id)
818    }
819
820    // --- Idempotent Repository Registry ---
821
822    /// Register an idempotent repository.
823    ///
824    /// Returns `Err(RegistryError::AlreadyRegistered)` if a repository with
825    /// the same name is already registered.
826    pub fn register_idempotent_repository(
827        &mut self,
828        name: impl Into<String>,
829        repo: Arc<dyn camel_api::IdempotentRepository>,
830    ) -> Result<(), RegistryError> {
831        self.idempotent_repositories.register(name, repo)
832    }
833
834    /// Retrieve an idempotent repository by name.
835    pub fn idempotent_repository(
836        &self,
837        name: &str,
838    ) -> Option<Arc<dyn camel_api::IdempotentRepository>> {
839        self.idempotent_repositories.get(name)
840    }
841
842    // --- Claim Check Repository Registry ---
843
844    /// Register a claim check repository.
845    ///
846    /// Returns `Err(RegistryError::AlreadyRegistered)` if a repository with
847    /// the same name is already registered.
848    pub fn register_claim_check_repository(
849        &mut self,
850        name: impl Into<String>,
851        repo: Arc<dyn camel_api::ClaimCheckRepository>,
852    ) -> Result<(), RegistryError> {
853        self.claim_check_repositories.register(name, repo)
854    }
855
856    /// Retrieve a claim check repository by name.
857    pub fn claim_check_repository(
858        &self,
859        name: &str,
860    ) -> Option<Arc<dyn camel_api::ClaimCheckRepository>> {
861        self.claim_check_repositories.get(name)
862    }
863}
864
865impl ComponentRegistrar for CamelContext {
866    fn register_component_dyn(&mut self, component: Arc<dyn Component>) {
867        let scheme = component.scheme().to_string();
868        self.registry
869            .lock()
870            .expect("mutex poisoned: another thread panicked while holding this lock") // allow-unwrap
871            .register(component);
872        trace!(scheme, "Registered component");
873    }
874}
875
876impl ComponentContext for CamelContext {
877    fn resolve_component(&self, scheme: &str) -> Option<Arc<dyn Component>> {
878        self.registry.lock().ok()?.get(scheme)
879    }
880
881    fn resolve_language(&self, name: &str) -> Option<Arc<dyn Language>> {
882        self.languages.lock().ok()?.get(name).cloned()
883    }
884
885    fn metrics(&self) -> Arc<dyn MetricsCollector> {
886        Arc::clone(&self.metrics)
887    }
888
889    fn health(&self) -> Arc<dyn camel_component_api::HealthCheckRegistry> {
890        // The concrete HealthCheckRegistry struct implements the trait via
891        // the impl added in health_registry.rs.
892        Arc::clone(&self.health_registry) as Arc<dyn camel_component_api::HealthCheckRegistry>
893    }
894
895    fn platform_service(&self) -> Arc<dyn PlatformService> {
896        Arc::clone(&self.platform_service)
897    }
898
899    fn register_route_health_check(
900        &self,
901        route_id: &str,
902        check: Arc<dyn camel_api::AsyncHealthCheck>,
903    ) {
904        self.health_registry.register_for_route(route_id, check);
905    }
906
907    fn unregister_route_health_check(&self, route_id: &str) {
908        self.health_registry.unregister_for_route(route_id);
909    }
910}
911
912#[async_trait::async_trait]
913impl camel_api::HealthSource for CamelContext {
914    async fn liveness(&self) -> camel_api::HealthStatus {
915        let has_failed = self
916            .services
917            .iter()
918            .any(|s| s.status() == camel_api::ServiceStatus::Failed);
919        if has_failed {
920            camel_api::HealthStatus::Unhealthy
921        } else {
922            camel_api::HealthStatus::Healthy
923        }
924    }
925
926    async fn readiness(&self) -> camel_api::HealthStatus {
927        let has_failed = self
928            .services
929            .iter()
930            .any(|s| s.status() == camel_api::ServiceStatus::Failed);
931        if has_failed {
932            return camel_api::HealthStatus::Unhealthy;
933        }
934        let has_stopped = self
935            .services
936            .iter()
937            .any(|s| s.status() == camel_api::ServiceStatus::Stopped);
938        if has_stopped {
939            return camel_api::HealthStatus::Degraded;
940        }
941        self.health_registry.check_all().await.status
942    }
943
944    async fn health_report(&self) -> camel_api::HealthReport {
945        let mut report = self.health_registry.check_all().await;
946        let mut worst = report.status;
947        for service in &self.services {
948            let svc_status = service.status();
949            let health = match svc_status {
950                camel_api::ServiceStatus::Started => camel_api::HealthStatus::Healthy,
951                camel_api::ServiceStatus::Stopped => camel_api::HealthStatus::Degraded,
952                camel_api::ServiceStatus::Failed => camel_api::HealthStatus::Unhealthy,
953            };
954            if matches!(worst, camel_api::HealthStatus::Healthy)
955                && matches!(
956                    health,
957                    camel_api::HealthStatus::Degraded | camel_api::HealthStatus::Unhealthy
958                )
959            {
960                worst = health;
961            }
962            if matches!(worst, camel_api::HealthStatus::Degraded)
963                && matches!(health, camel_api::HealthStatus::Unhealthy)
964            {
965                worst = health;
966            }
967            report.services.push(camel_api::ServiceHealth {
968                name: service.name().to_string(),
969                status: svc_status,
970                message: None,
971            });
972        }
973        report.status = worst;
974        report
975    }
976
977    async fn startup(&self) -> camel_api::HealthStatus {
978        camel_api::HealthStatus::Healthy
979    }
980}
981
982#[cfg(test)]
983#[path = "context_tests.rs"]
984mod context_tests;