1use std::any::{Any, TypeId};
2use std::collections::HashMap;
3use std::sync::Arc;
4use std::sync::atomic::{AtomicU64, Ordering};
5use tokio::time::timeout;
6use tokio_util::sync::CancellationToken;
7use tracing::{info, warn};
8
9#[cfg(test)]
10use camel_api::StepLifecycle;
11use camel_api::error_handler::ErrorHandlerConfig;
12use camel_api::{
13 CamelError, FunctionInvoker, HealthReport, Lifecycle, MetricsCollector, PlatformIdentity,
14 PlatformService, ReadinessGate, RouteTemplateSpec, RuntimeCommandBus, RuntimeQueryBus,
15 TemplateInstanceRecord,
16};
17use camel_component_api::{Component, ComponentContext, ComponentRegistrar};
18use camel_language_api::Language;
19
20use crate::health_registry::HealthCheckRegistry;
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::lifecycle::domain::LanguageRegistryError;
26use crate::registry::RegistryError;
27use crate::shared::components::domain::Registry;
28use crate::shared::observability::domain::TracerConfig;
29use crate::startup_validation::{ConfigCheck, run_startup_validation};
30use crate::template::TemplateRegistry;
31
32static CONTEXT_COMMAND_SEQ: AtomicU64 = AtomicU64::new(0);
33
34pub use crate::context_builder::CamelContextBuilder;
35
36pub struct CamelContext {
44 registry: Arc<std::sync::Mutex<Registry>>,
45 route_controller: RouteControllerHandle,
46 _actor_join: tokio::task::JoinHandle<()>,
47 supervision_join: Option<tokio::task::JoinHandle<()>>,
48 runtime: Arc<RuntimeBus>,
49 cancel_token: CancellationToken,
50 metrics: Arc<dyn MetricsCollector>,
51 platform_service: Arc<dyn PlatformService>,
53 languages: SharedLanguageRegistry,
54 shutdown_timeout: std::time::Duration,
55 services: Vec<Box<dyn Lifecycle>>,
56 health_registry: Arc<HealthCheckRegistry>,
57 component_configs: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
58 function_invoker: Option<Arc<dyn FunctionInvoker>>,
59 template_registry: Arc<TemplateRegistry>,
60 idempotent_repositories: crate::registry::SharedIdempotentRegistry,
61 claim_check_repositories: crate::registry::SharedClaimCheckRegistry,
62 startup_checks: Vec<Box<dyn ConfigCheck>>,
66}
67
68pub(crate) struct FromParts {
71 pub(crate) registry: Arc<std::sync::Mutex<Registry>>,
72 pub(crate) route_controller: RouteControllerHandle,
73 pub(crate) _actor_join: tokio::task::JoinHandle<()>,
74 pub(crate) supervision_join: Option<tokio::task::JoinHandle<()>>,
75 pub(crate) runtime: Arc<RuntimeBus>,
76 pub(crate) cancel_token: CancellationToken,
77 pub(crate) metrics: Arc<dyn MetricsCollector>,
78 pub(crate) platform_service: Arc<dyn PlatformService>,
79 pub(crate) languages: SharedLanguageRegistry,
80 pub(crate) shutdown_timeout: std::time::Duration,
81 pub(crate) services: Vec<Box<dyn Lifecycle>>,
82 pub(crate) health_registry: Arc<HealthCheckRegistry>,
83 pub(crate) component_configs: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
84 pub(crate) function_invoker: Option<Arc<dyn FunctionInvoker>>,
85 pub(crate) template_registry: Arc<TemplateRegistry>,
86 pub(crate) idempotent_repositories: crate::registry::SharedIdempotentRegistry,
87 pub(crate) claim_check_repositories: crate::registry::SharedClaimCheckRegistry,
88 pub(crate) startup_checks: Vec<Box<dyn ConfigCheck>>,
89}
90
91impl CamelContext {
92 pub(crate) fn from_parts(parts: FromParts) -> Self {
93 Self {
94 registry: parts.registry,
95 route_controller: parts.route_controller,
96 _actor_join: parts._actor_join,
97 supervision_join: parts.supervision_join,
98 runtime: parts.runtime,
99 cancel_token: parts.cancel_token,
100 metrics: parts.metrics,
101 platform_service: parts.platform_service,
102 languages: parts.languages,
103 shutdown_timeout: parts.shutdown_timeout,
104 services: parts.services,
105 health_registry: parts.health_registry,
106 component_configs: parts.component_configs,
107 function_invoker: parts.function_invoker,
108 template_registry: parts.template_registry,
109 idempotent_repositories: parts.idempotent_repositories,
110 claim_check_repositories: parts.claim_check_repositories,
111 startup_checks: parts.startup_checks,
112 }
113 }
114}
115
116#[derive(Clone)]
120pub struct RuntimeExecutionHandle {
121 pub(crate) controller: RouteControllerHandle,
122 pub(crate) runtime: Arc<RuntimeBus>,
123 pub(crate) function_invoker: Option<Arc<dyn FunctionInvoker>>,
124 #[cfg(test)]
128 #[allow(clippy::type_complexity)]
129 pub(crate) test_lifecycle_inject: Arc<std::sync::Mutex<Option<Vec<Arc<dyn StepLifecycle>>>>>,
130}
131
132impl RuntimeExecutionHandle {
133 pub(crate) async fn add_route_definition(
134 &self,
135 definition: RouteDefinition,
136 ) -> Result<(), CamelError> {
137 use crate::lifecycle::ports::RouteRegistrationPort;
138 self.runtime
139 .register_route(definition)
140 .await
141 .map_err(Into::into)
142 }
143
144 #[allow(dead_code)]
147 pub(crate) async fn compile_route_definition(
148 &self,
149 definition: RouteDefinition,
150 ) -> Result<camel_api::BoxProcessor, CamelError> {
151 self.controller.compile_route_definition(definition).await
152 }
153
154 #[allow(dead_code)] pub(crate) async fn compile_route_definition_with_generation(
156 &self,
157 definition: RouteDefinition,
158 generation: u64,
159 ) -> Result<camel_api::BoxProcessor, CamelError> {
160 self.controller
161 .compile_route_definition_with_generation(definition, generation)
162 .await
163 }
164
165 pub(crate) async fn compile_route_definition_pipeline(
166 &self,
167 definition: RouteDefinition,
168 generation: u64,
169 ) -> Result<crate::lifecycle::adapters::route_helpers::CompiledPipeline, CamelError> {
170 self.controller
171 .compile_route_definition_pipeline(definition, generation)
172 .await
173 }
174
175 pub(crate) async fn compile_route_definition_dry_pipeline(
178 &self,
179 definition: RouteDefinition,
180 ) -> Result<crate::lifecycle::adapters::route_helpers::CompiledPipeline, CamelError> {
181 self.controller
182 .compile_route_definition_dry_pipeline(definition)
183 .await
184 }
185
186 pub(crate) async fn prepare_route_definition_with_generation(
187 &self,
188 definition: RouteDefinition,
189 generation: u64,
190 ) -> Result<crate::lifecycle::adapters::route_controller::PreparedRoute, CamelError> {
191 self.controller
192 .prepare_route_definition_with_generation(definition, generation)
193 .await
194 }
195
196 pub(crate) async fn insert_prepared_route(
197 &self,
198 prepared: crate::lifecycle::adapters::route_controller::PreparedRoute,
199 ) -> Result<(), CamelError> {
200 self.controller.insert_prepared_route(prepared).await
201 }
202
203 pub(crate) async fn remove_route_preserving_functions(
204 &self,
205 route_id: String,
206 ) -> Result<(), CamelError> {
207 self.controller
208 .remove_route_preserving_functions(route_id)
209 .await
210 }
211
212 pub(crate) async fn register_route_aggregate(
213 &self,
214 route_id: String,
215 ) -> Result<(), CamelError> {
216 self.runtime.register_aggregate_only(route_id).await
217 }
218
219 pub(crate) async fn swap_route_pipeline(
220 &self,
221 route_id: &str,
222 pipeline: camel_api::BoxProcessor,
223 ) -> Result<(), CamelError> {
224 self.controller.swap_pipeline(route_id, pipeline).await
225 }
226
227 pub(crate) async fn stop_route_reload(&self, route_id: &str) -> Result<(), CamelError> {
229 self.controller.stop_route_reload(route_id).await
230 }
231
232 pub(crate) async fn start_route_reload(&self, route_id: &str) -> Result<(), CamelError> {
234 self.controller.start_route_reload(route_id).await
235 }
236
237 pub(crate) async fn swap_route_pipeline_raw(
240 &self,
241 route_id: &str,
242 pipeline: camel_api::BoxProcessor,
243 lifecycle: Vec<Arc<dyn camel_api::StepLifecycle>>,
244 ) -> Result<(), CamelError> {
245 self.controller
246 .swap_pipeline_raw(route_id, pipeline, lifecycle)
247 .await
248 }
249
250 pub(crate) async fn execute_runtime_command(
251 &self,
252 cmd: camel_api::RuntimeCommand,
253 ) -> Result<camel_api::RuntimeCommandResult, CamelError> {
254 self.runtime.execute(cmd).await
255 }
256
257 pub(crate) async fn runtime_route_status(
258 &self,
259 route_id: &str,
260 ) -> Result<Option<String>, CamelError> {
261 match self
262 .runtime
263 .ask(camel_api::RuntimeQuery::GetRouteStatus {
264 route_id: route_id.to_string(),
265 })
266 .await
267 {
268 Ok(camel_api::RuntimeQueryResult::RouteStatus { status, .. }) => Ok(Some(status)),
269 Ok(_) => Err(CamelError::RouteError(
270 "unexpected runtime query response for route status".to_string(),
271 )),
272 Err(CamelError::RouteError(msg)) if msg.contains("not found") => Ok(None),
273 Err(err) => Err(err),
274 }
275 }
276
277 pub(crate) async fn runtime_route_ids(&self) -> Result<Vec<String>, CamelError> {
278 match self.runtime.ask(camel_api::RuntimeQuery::ListRoutes).await {
279 Ok(camel_api::RuntimeQueryResult::Routes { route_ids }) => Ok(route_ids),
280 Ok(_) => Err(CamelError::RouteError(
281 "unexpected runtime query response for route listing".to_string(),
282 )),
283 Err(err) => Err(err),
284 }
285 }
286
287 pub(crate) async fn route_source_hash(&self, route_id: &str) -> Option<u64> {
288 self.controller.route_source_hash(route_id).await
289 }
290
291 pub(crate) async fn in_flight_count(&self, route_id: &str) -> Result<u64, CamelError> {
292 if !self.controller.route_exists(route_id).await? {
293 return Err(CamelError::RouteError(format!(
294 "Route '{}' not found",
295 route_id
296 )));
297 }
298 Ok(self
299 .controller
300 .in_flight_count(route_id)
301 .await?
302 .unwrap_or(0))
303 }
304
305 pub(crate) async fn route_has_lifecycle(&self, route_id: &str) -> bool {
307 self.controller
308 .route_has_lifecycle(route_id)
309 .await
310 .unwrap_or(false)
311 }
312
313 pub(crate) fn function_invoker(&self) -> Option<Arc<dyn FunctionInvoker>> {
314 self.function_invoker.clone()
315 }
316
317 #[cfg(test)]
318 pub(crate) async fn force_start_route_for_test(
319 &self,
320 route_id: &str,
321 ) -> Result<(), CamelError> {
322 self.controller.start_route(route_id).await
323 }
324
325 pub async fn controller_route_count_for_test(&self) -> usize {
326 self.controller.route_count().await.unwrap_or(0)
327 }
328}
329
330impl CamelContext {
331 pub fn builder() -> CamelContextBuilder {
332 CamelContextBuilder::new()
333 }
334
335 pub async fn set_error_handler(&mut self, config: ErrorHandlerConfig) {
337 let _ = self.route_controller.set_error_handler(config).await;
338 }
339
340 pub async fn set_tracing(&mut self, enabled: bool) {
342 let _ = self
343 .route_controller
344 .set_tracer_config(TracerConfig {
345 enabled,
346 ..Default::default()
347 })
348 .await;
349 }
350
351 pub async fn set_tracer_config(&mut self, config: TracerConfig) {
353 let config = if config.metrics_collector.is_none() {
355 TracerConfig {
356 metrics_collector: Some(Arc::clone(&self.metrics)),
357 ..config
358 }
359 } else {
360 config
361 };
362
363 let _ = self.route_controller.set_tracer_config(config).await;
364 }
365
366 pub async fn with_tracing(mut self) -> Self {
368 self.set_tracing(true).await;
369 self
370 }
371
372 pub async fn with_tracer_config(mut self, config: TracerConfig) -> Self {
376 self.set_tracer_config(config).await;
377 self
378 }
379
380 pub fn with_lifecycle<L: Lifecycle + 'static>(mut self, service: L) -> Self {
389 if let Some(collector) = service.as_metrics_collector() {
390 self.metrics = collector;
391 }
392 if let Some(invoker) = service.as_function_invoker() {
393 self.function_invoker = Some(invoker.clone());
394 if let Err(e) = self.route_controller.try_set_function_invoker(invoker) {
395 tracing::warn!("Failed to propagate function invoker to route controller: {e}");
396 }
397 }
398
399 self.services.push(Box::new(service));
400 self
401 }
402
403 pub fn register_component<C: Component + 'static>(&mut self, component: C) {
405 info!(scheme = component.scheme(), "Registering component");
406 self.registry
407 .lock()
408 .expect("mutex poisoned: another thread panicked while holding this lock") .register(Arc::new(component));
410 }
411
412 pub fn add_startup_check(&mut self, check: Box<dyn ConfigCheck>) {
421 self.startup_checks.push(check);
422 }
423
424 pub fn register_language(
431 &mut self,
432 name: impl Into<String>,
433 lang: Box<dyn Language>,
434 ) -> Result<(), LanguageRegistryError> {
435 let name = name.into();
436 let mut languages = self
437 .languages
438 .lock()
439 .expect("mutex poisoned: another thread panicked while holding this lock"); if languages.contains_key(&name) {
441 return Err(LanguageRegistryError::AlreadyRegistered { name });
442 }
443 languages.insert(name, Arc::from(lang));
444 Ok(())
445 }
446
447 pub fn resolve_language(&self, name: &str) -> Option<Arc<dyn Language>> {
449 let languages = self
450 .languages
451 .lock()
452 .expect("mutex poisoned: another thread panicked while holding this lock"); languages.get(name).cloned()
454 }
455
456 pub async fn add_route_definition(
460 &self,
461 definition: RouteDefinition,
462 ) -> Result<(), CamelError> {
463 use crate::lifecycle::ports::RouteRegistrationPort;
464 info!(
465 from = definition.from_uri(),
466 route_id = %definition.route_id(),
467 "Adding route definition"
468 );
469 self.runtime
470 .register_route(definition)
471 .await
472 .map_err(Into::into)
473 }
474
475 fn next_context_command_id(op: &str, route_id: &str) -> String {
476 let seq = CONTEXT_COMMAND_SEQ.fetch_add(1, Ordering::Relaxed);
477 format!("context:{op}:{route_id}:{seq}")
478 }
479
480 pub fn registry(&self) -> std::sync::MutexGuard<'_, Registry> {
482 self.registry
483 .lock()
484 .expect("mutex poisoned: another thread panicked while holding this lock") }
486
487 pub fn registry_arc(&self) -> Arc<std::sync::Mutex<Registry>> {
489 Arc::clone(&self.registry)
490 }
491
492 pub fn runtime_execution_handle(&self) -> RuntimeExecutionHandle {
494 RuntimeExecutionHandle {
495 controller: self.route_controller.clone(),
496 runtime: Arc::clone(&self.runtime),
497 function_invoker: self.function_invoker.clone(),
498 #[cfg(test)]
499 test_lifecycle_inject: Arc::new(std::sync::Mutex::new(None)),
500 }
501 }
502
503 pub fn metrics(&self) -> Arc<dyn MetricsCollector> {
505 Arc::clone(&self.metrics)
506 }
507
508 pub fn platform_service(&self) -> Arc<dyn PlatformService> {
510 Arc::clone(&self.platform_service)
511 }
512
513 pub fn readiness_gate(&self) -> Arc<dyn ReadinessGate> {
515 self.platform_service.readiness_gate()
516 }
517
518 pub fn platform_identity(&self) -> PlatformIdentity {
520 self.platform_service.identity()
521 }
522
523 pub fn leadership(&self) -> Arc<dyn camel_api::LeadershipService> {
525 self.platform_service.leadership()
526 }
527
528 pub fn runtime(&self) -> Arc<dyn camel_api::RuntimeHandle> {
530 self.runtime.clone()
531 }
532
533 pub fn producer_context(&self) -> camel_api::ProducerContext {
535 camel_api::ProducerContext::new().with_runtime(self.runtime())
536 }
537
538 pub async fn runtime_route_status(&self, route_id: &str) -> Result<Option<String>, CamelError> {
540 match self
541 .runtime()
542 .ask(camel_api::RuntimeQuery::GetRouteStatus {
543 route_id: route_id.to_string(),
544 })
545 .await
546 {
547 Ok(camel_api::RuntimeQueryResult::RouteStatus { status, .. }) => Ok(Some(status)),
548 Ok(_) => Err(CamelError::RouteError(
549 "unexpected runtime query response for route status".to_string(),
550 )),
551 Err(CamelError::RouteError(msg)) if msg.contains("not found") => Ok(None),
552 Err(err) => Err(err),
553 }
554 }
555
556 pub async fn start(&mut self) -> Result<(), CamelError> {
561 info!("Starting CamelContext");
562
563 for (i, service) in self.services.iter_mut().enumerate() {
565 info!("Starting service: {}", service.name());
566 if let Err(e) = service.start().await {
567 warn!(
569 "Service {} failed to start, rolling back {} services",
570 service.name(),
571 i
572 );
573 for j in (0..i).rev() {
574 if let Err(rollback_err) = self.services[j].stop().await {
575 warn!(
576 "Failed to stop service {} during rollback: {}",
577 self.services[j].name(),
578 rollback_err
579 );
580 }
581 }
582 return Err(e);
583 }
584 }
585
586 let checks = std::mem::take(&mut self.startup_checks);
592 if let Err(e) = run_startup_validation(checks) {
593 warn!("Startup validation failed: {e}");
594 return Err(e);
595 }
596
597 self.runtime
600 .reconcile_transient_states()
601 .await
602 .map_err(|e| CamelError::RouteError(format!("boot reconciliation failed: {e}")))?;
603
604 let route_ids = self.route_controller.auto_startup_route_ids().await?;
607 for route_id in route_ids {
608 self.runtime
609 .execute(camel_api::RuntimeCommand::StartRoute {
610 route_id: route_id.clone(),
611 command_id: Self::next_context_command_id("start", &route_id),
612 causation_id: None,
613 })
614 .await?;
615 }
616
617 info!("CamelContext started");
618 Ok(())
619 }
620
621 pub async fn stop(&mut self) -> Result<(), CamelError> {
623 self.stop_timeout(self.shutdown_timeout).await
624 }
625
626 pub async fn stop_timeout(&mut self, _timeout: std::time::Duration) -> Result<(), CamelError> {
633 info!("Stopping CamelContext");
634
635 self.cancel_token.cancel();
637 if let Some(join) = self.supervision_join.take() {
638 join.abort();
639 }
640
641 let route_ids = self.route_controller.shutdown_route_ids().await?;
644 for route_id in route_ids {
645 if let Err(err) = self
646 .runtime
647 .execute(camel_api::RuntimeCommand::StopRoute {
648 route_id: route_id.clone(),
649 command_id: Self::next_context_command_id("stop", &route_id),
650 causation_id: None,
651 })
652 .await
653 {
654 warn!(route_id = %route_id, error = %err, "Runtime stop command failed during context shutdown");
655 }
656 }
657
658 self.health_registry.cancel_token().cancel();
659
660 let mut first_error = None;
663 for service in self.services.iter_mut().rev() {
664 info!("Stopping service: {}", service.name());
665 if let Err(e) = service.stop().await {
666 warn!("Service {} failed to stop: {}", service.name(), e);
667 if first_error.is_none() {
668 first_error = Some(e);
669 }
670 }
671 }
672
673 info!("CamelContext stopped");
674
675 if let Some(e) = first_error {
676 Err(e)
677 } else {
678 Ok(())
679 }
680 }
681
682 pub fn shutdown_timeout(&self) -> std::time::Duration {
684 self.shutdown_timeout
685 }
686
687 pub fn set_shutdown_timeout(&mut self, timeout: std::time::Duration) {
689 self.shutdown_timeout = timeout;
690 }
691
692 pub async fn abort(&mut self) {
694 self.cancel_token.cancel();
695 if let Some(join) = self.supervision_join.take() {
696 join.abort();
697 }
698 let route_ids = self
699 .route_controller
700 .shutdown_route_ids()
701 .await
702 .unwrap_or_default();
703 for route_id in route_ids {
704 let _ = self
705 .runtime
706 .execute(camel_api::RuntimeCommand::StopRoute {
707 route_id: route_id.clone(),
708 command_id: Self::next_context_command_id("abort-stop", &route_id),
709 causation_id: None,
710 })
711 .await;
712 }
713
714 for service in self.services.iter_mut().rev() {
715 let name = service.name().to_string();
716 match timeout(std::time::Duration::from_secs(5), service.stop()).await {
717 Ok(Ok(())) => info!("Aborted service: {}", name),
718 Ok(Err(e)) => warn!("Service {} failed to stop during abort: {}", name, e),
719 Err(_) => warn!("Service {} timed out during abort (5s)", name),
720 }
721 }
722 }
723
724 pub async fn health_check(&self) -> HealthReport {
726 use camel_api::HealthSource;
727 self.health_report().await
728 }
729
730 pub fn health_registry(&self) -> Arc<HealthCheckRegistry> {
731 Arc::clone(&self.health_registry)
732 }
733
734 pub fn set_component_config<T: 'static + Send + Sync>(&mut self, config: T) {
736 self.component_configs
737 .insert(TypeId::of::<T>(), Box::new(config));
738 }
739
740 pub fn get_component_config<T: 'static + Send + Sync>(&self) -> Option<&T> {
742 self.component_configs
743 .get(&TypeId::of::<T>())
744 .and_then(|b| b.downcast_ref::<T>())
745 }
746
747 pub fn add_route_template(&self, spec: RouteTemplateSpec) -> Result<(), CamelError> {
753 self.template_registry.register(spec)
754 }
755
756 pub fn get_route_template(&self, id: &str) -> Option<RouteTemplateSpec> {
758 self.template_registry.get(id)
759 }
760
761 pub fn template_ids(&self) -> Vec<String> {
763 self.template_registry.template_ids()
764 }
765
766 pub fn record_template_instance(&self, record: TemplateInstanceRecord) {
768 self.template_registry.record_instance(record)
769 }
770
771 pub fn template_instances(&self, template_id: &str) -> Vec<TemplateInstanceRecord> {
773 self.template_registry.instances(template_id)
774 }
775
776 pub fn register_idempotent_repository(
783 &mut self,
784 name: impl Into<String>,
785 repo: Arc<dyn camel_api::IdempotentRepository>,
786 ) -> Result<(), RegistryError> {
787 self.idempotent_repositories.register(name, repo)
788 }
789
790 pub fn idempotent_repository(
792 &self,
793 name: &str,
794 ) -> Option<Arc<dyn camel_api::IdempotentRepository>> {
795 self.idempotent_repositories.get(name)
796 }
797
798 pub fn register_claim_check_repository(
805 &mut self,
806 name: impl Into<String>,
807 repo: Arc<dyn camel_api::ClaimCheckRepository>,
808 ) -> Result<(), RegistryError> {
809 self.claim_check_repositories.register(name, repo)
810 }
811
812 pub fn claim_check_repository(
814 &self,
815 name: &str,
816 ) -> Option<Arc<dyn camel_api::ClaimCheckRepository>> {
817 self.claim_check_repositories.get(name)
818 }
819}
820
821impl ComponentRegistrar for CamelContext {
822 fn register_component_dyn(&mut self, component: Arc<dyn Component>) {
823 self.registry
824 .lock()
825 .expect("mutex poisoned: another thread panicked while holding this lock") .register(component);
827 }
828}
829
830impl ComponentContext for CamelContext {
831 fn resolve_component(&self, scheme: &str) -> Option<Arc<dyn Component>> {
832 self.registry.lock().ok()?.get(scheme)
833 }
834
835 fn resolve_language(&self, name: &str) -> Option<Arc<dyn Language>> {
836 self.languages.lock().ok()?.get(name).cloned()
837 }
838
839 fn metrics(&self) -> Arc<dyn MetricsCollector> {
840 Arc::clone(&self.metrics)
841 }
842
843 fn health(&self) -> Arc<dyn camel_component_api::HealthCheckRegistry> {
844 Arc::clone(&self.health_registry) as Arc<dyn camel_component_api::HealthCheckRegistry>
847 }
848
849 fn platform_service(&self) -> Arc<dyn PlatformService> {
850 Arc::clone(&self.platform_service)
851 }
852
853 fn register_route_health_check(
854 &self,
855 route_id: &str,
856 check: Arc<dyn camel_api::AsyncHealthCheck>,
857 ) {
858 self.health_registry.register_for_route(route_id, check);
859 }
860
861 fn unregister_route_health_check(&self, route_id: &str) {
862 self.health_registry.unregister_for_route(route_id);
863 }
864}
865
866#[async_trait::async_trait]
867impl camel_api::HealthSource for CamelContext {
868 async fn liveness(&self) -> camel_api::HealthStatus {
869 let has_failed = self
870 .services
871 .iter()
872 .any(|s| s.status() == camel_api::ServiceStatus::Failed);
873 if has_failed {
874 camel_api::HealthStatus::Unhealthy
875 } else {
876 camel_api::HealthStatus::Healthy
877 }
878 }
879
880 async fn readiness(&self) -> camel_api::HealthStatus {
881 let has_failed = self
882 .services
883 .iter()
884 .any(|s| s.status() == camel_api::ServiceStatus::Failed);
885 if has_failed {
886 return camel_api::HealthStatus::Unhealthy;
887 }
888 let has_stopped = self
889 .services
890 .iter()
891 .any(|s| s.status() == camel_api::ServiceStatus::Stopped);
892 if has_stopped {
893 return camel_api::HealthStatus::Degraded;
894 }
895 self.health_registry.check_all().await.status
896 }
897
898 async fn health_report(&self) -> camel_api::HealthReport {
899 let mut report = self.health_registry.check_all().await;
900 let mut worst = report.status;
901 for service in &self.services {
902 let svc_status = service.status();
903 let health = match svc_status {
904 camel_api::ServiceStatus::Started => camel_api::HealthStatus::Healthy,
905 camel_api::ServiceStatus::Stopped => camel_api::HealthStatus::Degraded,
906 camel_api::ServiceStatus::Failed => camel_api::HealthStatus::Unhealthy,
907 };
908 if matches!(worst, camel_api::HealthStatus::Healthy)
909 && matches!(
910 health,
911 camel_api::HealthStatus::Degraded | camel_api::HealthStatus::Unhealthy
912 )
913 {
914 worst = health;
915 }
916 if matches!(worst, camel_api::HealthStatus::Degraded)
917 && matches!(health, camel_api::HealthStatus::Unhealthy)
918 {
919 worst = health;
920 }
921 report.services.push(camel_api::ServiceHealth {
922 name: service.name().to_string(),
923 status: svc_status,
924 message: None,
925 });
926 }
927 report.status = worst;
928 report
929 }
930
931 async fn startup(&self) -> camel_api::HealthStatus {
932 camel_api::HealthStatus::Healthy
933 }
934}
935
936#[cfg(test)]
937#[path = "context_tests.rs"]
938mod context_tests;