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::{debug, info, trace, warn};
8
9#[cfg(test)]
10use camel_api::StepLifecycle;
11use camel_api::component_metadata::ComponentMetadata;
12use camel_api::error_handler::ErrorHandlerConfig;
13use camel_api::{
14 CamelError, FunctionInvoker, HealthReport, Lifecycle, MetricsCollector, PlatformIdentity,
15 PlatformService, ReadinessGate, RouteTemplateSpec, RuntimeCommandBus, RuntimeQueryBus,
16 TemplateInstanceRecord,
17};
18use camel_component_api::{Component, ComponentContext, ComponentRegistrar};
19use camel_language_api::Language;
20
21use crate::health_registry::HealthCheckRegistry;
22use crate::lifecycle::adapters::controller_actor::RouteControllerHandle;
23use crate::lifecycle::adapters::route_controller::SharedLanguageRegistry;
24use crate::lifecycle::application::route_definition::RouteDefinition;
25use crate::lifecycle::application::runtime_bus::RuntimeBus;
26use crate::lifecycle::domain::LanguageRegistryError;
27use crate::registry::RegistryError;
28use crate::shared::components::domain::Registry;
29use crate::shared::observability::domain::TracerConfig;
30use crate::startup_validation::{ConfigCheck, run_startup_validation};
31use crate::template::TemplateRegistry;
32
33static CONTEXT_COMMAND_SEQ: AtomicU64 = AtomicU64::new(0);
34
35pub use crate::context_builder::CamelContextBuilder;
36
37pub struct CamelContext {
46 registry: Arc<std::sync::Mutex<Registry>>,
47 route_controller: RouteControllerHandle,
48 actor_join: Option<tokio::task::JoinHandle<()>>,
49 supervision_join: Option<tokio::task::JoinHandle<()>>,
50 runtime: Arc<RuntimeBus>,
51 cancel_token: CancellationToken,
52 metrics: Arc<dyn MetricsCollector>,
53 platform_service: Arc<dyn PlatformService>,
55 languages: SharedLanguageRegistry,
56 shutdown_timeout: std::time::Duration,
57 services: Vec<Box<dyn Lifecycle>>,
58 health_registry: Arc<HealthCheckRegistry>,
59 component_configs: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
60 function_invoker: Option<Arc<dyn FunctionInvoker>>,
61 template_registry: Arc<TemplateRegistry>,
62 idempotent_repositories: crate::registry::SharedIdempotentRegistry,
63 claim_check_repositories: crate::registry::SharedClaimCheckRegistry,
64 startup_checks: Vec<Box<dyn ConfigCheck>>,
68}
69
70pub(crate) struct FromParts {
73 pub(crate) registry: Arc<std::sync::Mutex<Registry>>,
74 pub(crate) route_controller: RouteControllerHandle,
75 pub(crate) _actor_join: tokio::task::JoinHandle<()>,
76 pub(crate) supervision_join: Option<tokio::task::JoinHandle<()>>,
77 pub(crate) runtime: Arc<RuntimeBus>,
78 pub(crate) cancel_token: CancellationToken,
79 pub(crate) metrics: Arc<dyn MetricsCollector>,
80 pub(crate) platform_service: Arc<dyn PlatformService>,
81 pub(crate) languages: SharedLanguageRegistry,
82 pub(crate) shutdown_timeout: std::time::Duration,
83 pub(crate) services: Vec<Box<dyn Lifecycle>>,
84 pub(crate) health_registry: Arc<HealthCheckRegistry>,
85 pub(crate) component_configs: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
86 pub(crate) function_invoker: Option<Arc<dyn FunctionInvoker>>,
87 pub(crate) template_registry: Arc<TemplateRegistry>,
88 pub(crate) idempotent_repositories: crate::registry::SharedIdempotentRegistry,
89 pub(crate) claim_check_repositories: crate::registry::SharedClaimCheckRegistry,
90 pub(crate) startup_checks: Vec<Box<dyn ConfigCheck>>,
91}
92
93impl CamelContext {
94 pub(crate) fn from_parts(parts: FromParts) -> Self {
95 Self {
96 registry: parts.registry,
97 route_controller: parts.route_controller,
98 actor_join: Some(parts._actor_join),
99 supervision_join: parts.supervision_join,
100 runtime: parts.runtime,
101 cancel_token: parts.cancel_token,
102 metrics: parts.metrics,
103 platform_service: parts.platform_service,
104 languages: parts.languages,
105 shutdown_timeout: parts.shutdown_timeout,
106 services: parts.services,
107 health_registry: parts.health_registry,
108 component_configs: parts.component_configs,
109 function_invoker: parts.function_invoker,
110 template_registry: parts.template_registry,
111 idempotent_repositories: parts.idempotent_repositories,
112 claim_check_repositories: parts.claim_check_repositories,
113 startup_checks: parts.startup_checks,
114 }
115 }
116}
117
118#[derive(Clone)]
122pub struct RuntimeExecutionHandle {
123 pub(crate) controller: RouteControllerHandle,
124 pub(crate) runtime: Arc<RuntimeBus>,
125 pub(crate) function_invoker: Option<Arc<dyn FunctionInvoker>>,
126 #[cfg(test)]
130 #[allow(clippy::type_complexity)]
131 pub(crate) test_lifecycle_inject: Arc<std::sync::Mutex<Option<Vec<Arc<dyn StepLifecycle>>>>>,
132}
133
134impl RuntimeExecutionHandle {
135 pub(crate) async fn add_route_definition(
136 &self,
137 definition: RouteDefinition,
138 ) -> Result<(), CamelError> {
139 use crate::lifecycle::ports::RouteRegistrationPort;
140 self.runtime
141 .register_route(definition)
142 .await
143 .map_err(Into::into)
144 }
145
146 #[allow(dead_code)]
149 pub(crate) async fn compile_route_definition(
150 &self,
151 definition: RouteDefinition,
152 ) -> Result<camel_api::BoxProcessor, CamelError> {
153 self.controller.compile_route_definition(definition).await
154 }
155
156 #[allow(dead_code)] pub(crate) async fn compile_route_definition_with_generation(
158 &self,
159 definition: RouteDefinition,
160 generation: u64,
161 ) -> Result<camel_api::BoxProcessor, CamelError> {
162 self.controller
163 .compile_route_definition_with_generation(definition, generation)
164 .await
165 }
166
167 pub(crate) async fn compile_route_definition_pipeline(
168 &self,
169 definition: RouteDefinition,
170 generation: u64,
171 ) -> Result<crate::lifecycle::adapters::route_helpers::CompiledPipeline, CamelError> {
172 self.controller
173 .compile_route_definition_pipeline(definition, generation)
174 .await
175 }
176
177 pub(crate) async fn compile_route_definition_dry_pipeline(
180 &self,
181 definition: RouteDefinition,
182 ) -> Result<crate::lifecycle::adapters::route_helpers::CompiledPipeline, CamelError> {
183 self.controller
184 .compile_route_definition_dry_pipeline(definition)
185 .await
186 }
187
188 pub(crate) async fn prepare_route_definition_with_generation(
189 &self,
190 definition: RouteDefinition,
191 generation: u64,
192 ) -> Result<crate::lifecycle::adapters::route_controller::PreparedRoute, CamelError> {
193 self.controller
194 .prepare_route_definition_with_generation(definition, generation)
195 .await
196 }
197
198 pub(crate) async fn insert_prepared_route(
199 &self,
200 prepared: crate::lifecycle::adapters::route_controller::PreparedRoute,
201 ) -> Result<(), CamelError> {
202 self.controller.insert_prepared_route(prepared).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 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 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 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 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
332impl CamelContext {
333 pub fn builder() -> CamelContextBuilder {
334 CamelContextBuilder::new()
335 }
336
337 pub async fn set_error_handler(&mut self, config: ErrorHandlerConfig) {
339 let _ = self.route_controller.set_error_handler(config).await;
340 }
341
342 pub async fn set_tracing(&mut self, enabled: bool) {
344 let _ = self
345 .route_controller
346 .set_tracer_config(TracerConfig {
347 enabled,
348 ..Default::default()
349 })
350 .await;
351 }
352
353 pub async fn set_tracer_config(&mut self, config: TracerConfig) {
355 let config = if config.metrics_collector.is_none() {
357 TracerConfig {
358 metrics_collector: Some(Arc::clone(&self.metrics)),
359 ..config
360 }
361 } else {
362 config
363 };
364
365 let _ = self.route_controller.set_tracer_config(config).await;
366 }
367
368 pub async fn with_tracing(mut self) -> Self {
370 self.set_tracing(true).await;
371 self
372 }
373
374 pub async fn with_tracer_config(mut self, config: TracerConfig) -> Self {
378 self.set_tracer_config(config).await;
379 self
380 }
381
382 pub fn with_lifecycle<L: Lifecycle + 'static>(mut self, service: L) -> Self {
391 if let Some(collector) = service.as_metrics_collector() {
392 self.metrics = collector;
393 }
394 if let Some(invoker) = service.as_function_invoker() {
395 self.function_invoker = Some(invoker.clone());
396 if let Err(e) = self.route_controller.try_set_function_invoker(invoker) {
397 tracing::debug!("Failed to propagate function invoker to route controller: {e}");
398 }
399 }
400
401 self.services.push(Box::new(service));
402 self
403 }
404
405 pub fn register_component<C: Component + 'static>(&mut self, component: C) {
411 self.register_component_dyn(Arc::new(component));
412 }
413
414 pub fn add_startup_check(&mut self, check: Box<dyn ConfigCheck>) {
423 self.startup_checks.push(check);
424 }
425
426 pub fn register_language(
433 &mut self,
434 name: impl Into<String>,
435 lang: Box<dyn Language>,
436 ) -> Result<(), LanguageRegistryError> {
437 let name = name.into();
438 let mut languages = self
439 .languages
440 .lock()
441 .expect("mutex poisoned: another thread panicked while holding this lock"); if languages.contains_key(&name) {
443 return Err(LanguageRegistryError::AlreadyRegistered { name });
444 }
445 languages.insert(name, Arc::from(lang));
446 Ok(())
447 }
448
449 pub fn resolve_language(&self, name: &str) -> Option<Arc<dyn Language>> {
451 let languages = self
452 .languages
453 .lock()
454 .expect("mutex poisoned: another thread panicked while holding this lock"); languages.get(name).cloned()
456 }
457
458 pub async fn add_route_definition(
462 &self,
463 definition: RouteDefinition,
464 ) -> Result<(), CamelError> {
465 use crate::lifecycle::ports::RouteRegistrationPort;
466 debug!(
467 from = definition.from_uri(),
468 route_id = %definition.route_id(),
469 "Adding route definition"
470 );
471 self.runtime
472 .register_route(definition)
473 .await
474 .map_err(Into::into)
475 }
476
477 fn next_context_command_id(op: &str, route_id: &str) -> String {
478 let seq = CONTEXT_COMMAND_SEQ.fetch_add(1, Ordering::Relaxed);
479 format!("context:{op}:{route_id}:{seq}")
480 }
481
482 pub fn registry(&self) -> std::sync::MutexGuard<'_, Registry> {
484 self.registry
485 .lock()
486 .expect("mutex poisoned: another thread panicked while holding this lock") }
488
489 pub fn registry_arc(&self) -> Arc<std::sync::Mutex<Registry>> {
491 Arc::clone(&self.registry)
492 }
493
494 pub fn runtime_execution_handle(&self) -> RuntimeExecutionHandle {
496 RuntimeExecutionHandle {
497 controller: self.route_controller.clone(),
498 runtime: Arc::clone(&self.runtime),
499 function_invoker: self.function_invoker.clone(),
500 #[cfg(test)]
501 test_lifecycle_inject: Arc::new(std::sync::Mutex::new(None)),
502 }
503 }
504
505 pub fn metrics(&self) -> Arc<dyn MetricsCollector> {
507 Arc::clone(&self.metrics)
508 }
509
510 pub fn platform_service(&self) -> Arc<dyn PlatformService> {
512 Arc::clone(&self.platform_service)
513 }
514
515 pub fn readiness_gate(&self) -> Arc<dyn ReadinessGate> {
517 self.platform_service.readiness_gate()
518 }
519
520 pub fn platform_identity(&self) -> PlatformIdentity {
522 self.platform_service.identity()
523 }
524
525 pub fn leadership(&self) -> Arc<dyn camel_api::LeadershipService> {
527 self.platform_service.leadership()
528 }
529
530 pub fn runtime(&self) -> Arc<dyn camel_api::RuntimeHandle> {
532 self.runtime.clone()
533 }
534
535 pub fn producer_context(&self) -> camel_api::ProducerContext {
537 camel_api::ProducerContext::new().with_runtime(self.runtime())
538 }
539
540 pub async fn runtime_route_status(&self, route_id: &str) -> Result<Option<String>, CamelError> {
542 match self
543 .runtime()
544 .ask(camel_api::RuntimeQuery::GetRouteStatus {
545 route_id: route_id.to_string(),
546 })
547 .await
548 {
549 Ok(camel_api::RuntimeQueryResult::RouteStatus { status, .. }) => Ok(Some(status)),
550 Ok(_) => Err(CamelError::RouteError(
551 "unexpected runtime query response for route status".to_string(),
552 )),
553 Err(CamelError::RouteError(msg)) if msg.contains("not found") => Ok(None),
554 Err(err) => Err(err),
555 }
556 }
557
558 pub async fn start(&mut self) -> Result<(), CamelError> {
563 info!("Starting CamelContext");
564
565 self.cancel_token = CancellationToken::new();
567
568 for (i, service) in self.services.iter_mut().enumerate() {
570 info!("Starting service: {}", service.name());
571 if let Err(e) = service.start().await {
572 warn!(
574 "Service {} failed to start, rolling back {} services",
575 service.name(),
576 i
577 );
578 for j in (0..i).rev() {
579 if let Err(rollback_err) = self.services[j].stop().await {
580 warn!(
581 "Failed to stop service {} during rollback: {}",
582 self.services[j].name(),
583 rollback_err
584 );
585 }
586 }
587 return Err(e);
588 }
589 }
590
591 let checks = std::mem::take(&mut self.startup_checks);
597 if let Err(e) = run_startup_validation(checks) {
598 warn!("Startup validation failed: {e}");
599 return Err(e);
600 }
601
602 self.runtime
605 .reconcile_transient_states()
606 .await
607 .map_err(|e| CamelError::RouteError(format!("boot reconciliation failed: {e}")))?;
608
609 let route_ids = self.route_controller.auto_startup_route_ids().await?;
612 for route_id in route_ids {
613 self.runtime
614 .execute(camel_api::RuntimeCommand::StartRoute {
615 route_id: route_id.clone(),
616 command_id: Self::next_context_command_id("start", &route_id),
617 causation_id: None,
618 })
619 .await?;
620 }
621
622 info!("CamelContext started");
623 Ok(())
624 }
625
626 pub async fn stop(&mut self) -> Result<(), CamelError> {
628 self.stop_timeout(self.shutdown_timeout).await
629 }
630
631 pub async fn stop_timeout(&mut self, _timeout: std::time::Duration) -> Result<(), CamelError> {
638 info!("Stopping CamelContext");
639
640 self.cancel_token.cancel();
642 if let Some(join) = self.supervision_join.take() {
643 join.abort();
644 }
645
646 let route_ids = self.route_controller.shutdown_route_ids().await?;
649 for route_id in route_ids {
650 if let Err(err) = self
651 .runtime
652 .execute(camel_api::RuntimeCommand::StopRoute {
653 route_id: route_id.clone(),
654 command_id: Self::next_context_command_id("stop", &route_id),
655 causation_id: None,
656 })
657 .await
658 {
659 warn!(route_id = %route_id, error = %err, "Runtime stop command failed during context shutdown");
660 }
661 }
662
663 let mut first_error = None;
670 for service in self.services.iter_mut().rev() {
671 info!("Stopping service: {}", service.name());
672 if let Err(e) = service.stop().await {
673 warn!("Service {} failed to stop: {}", service.name(), e);
674 if first_error.is_none() {
675 first_error = Some(e);
676 }
677 }
678 }
679
680 info!("CamelContext stopped");
681
682 if let Some(e) = first_error {
683 Err(e)
684 } else {
685 Ok(())
686 }
687 }
688
689 pub fn shutdown_timeout(&self) -> std::time::Duration {
691 self.shutdown_timeout
692 }
693
694 pub fn set_shutdown_timeout(&mut self, timeout: std::time::Duration) {
696 self.shutdown_timeout = timeout;
697 }
698
699 #[cfg(test)]
702 pub(crate) fn take_actor_join(&mut self) -> Option<tokio::task::JoinHandle<()>> {
703 self.actor_join.take()
704 }
705
706 pub async fn abort(&mut self) {
708 self.cancel_token.cancel();
709 if let Some(join) = self.supervision_join.take() {
710 join.abort();
711 }
712 let route_ids = self
713 .route_controller
714 .shutdown_route_ids()
715 .await
716 .unwrap_or_default();
717 for route_id in route_ids {
718 let _ = self
719 .runtime
720 .execute(camel_api::RuntimeCommand::StopRoute {
721 route_id: route_id.clone(),
722 command_id: Self::next_context_command_id("abort-stop", &route_id),
723 causation_id: None,
724 })
725 .await;
726 }
727
728 for service in self.services.iter_mut().rev() {
729 let name = service.name().to_string();
730 match timeout(std::time::Duration::from_secs(5), service.stop()).await {
731 Ok(Ok(())) => info!("Aborted service: {}", name),
732 Ok(Err(e)) => warn!("Service {} failed to stop during abort: {}", name, e),
733 Err(_) => warn!("Service {} timed out during abort (5s)", name),
734 }
735 }
736
737 let _ = self.route_controller.shutdown().await;
740 self.health_registry.cancel_token().cancel();
741 if let Some(mut join) = self.actor_join.take() {
742 match tokio::time::timeout(std::time::Duration::from_secs(5), &mut join).await {
743 Ok(Ok(())) => {}
744 Ok(Err(e)) => warn!("Controller actor task error during abort: {e}"),
745 Err(_) => {
746 warn!("Controller actor did not stop within 5s during abort; force-aborting");
747 join.abort();
748 let _ = join.await;
749 }
750 }
751 }
752 }
753
754 pub async fn health_check(&self) -> HealthReport {
756 use camel_api::HealthSource;
757 self.health_report().await
758 }
759
760 pub fn health_registry(&self) -> Arc<HealthCheckRegistry> {
761 Arc::clone(&self.health_registry)
762 }
763
764 pub fn set_component_config<T: 'static + Send + Sync>(&mut self, config: T) {
766 self.component_configs
767 .insert(TypeId::of::<T>(), Box::new(config));
768 }
769
770 pub fn get_component_config<T: 'static + Send + Sync>(&self) -> Option<&T> {
772 self.component_configs
773 .get(&TypeId::of::<T>())
774 .and_then(|b| b.downcast_ref::<T>())
775 }
776
777 pub fn component_metadata(&self, scheme: &str) -> Option<ComponentMetadata> {
781 self.registry.lock().ok()?.get_metadata(scheme)
782 }
783
784 pub fn all_component_metadata(&self) -> Vec<ComponentMetadata> {
786 self.registry
787 .lock()
788 .expect("mutex poisoned: another thread panicked while holding this lock") .all_metadata()
790 }
791
792 pub fn metadata_catalog(
799 &self,
800 ) -> crate::component_metadata_catalog::RuntimeComponentMetadataCatalog {
801 crate::component_metadata_catalog::RuntimeComponentMetadataCatalog::new(Arc::clone(
802 &self.registry,
803 ))
804 }
805
806 pub fn add_route_template(&self, spec: RouteTemplateSpec) -> Result<(), CamelError> {
812 self.template_registry.register(spec)
813 }
814
815 pub fn get_route_template(&self, id: &str) -> Option<RouteTemplateSpec> {
817 self.template_registry.get(id)
818 }
819
820 pub fn template_ids(&self) -> Vec<String> {
822 self.template_registry.template_ids()
823 }
824
825 pub fn record_template_instance(&self, record: TemplateInstanceRecord) {
827 self.template_registry.record_instance(record)
828 }
829
830 pub fn template_instances(&self, template_id: &str) -> Vec<TemplateInstanceRecord> {
832 self.template_registry.instances(template_id)
833 }
834
835 pub fn register_idempotent_repository(
842 &mut self,
843 name: impl Into<String>,
844 repo: Arc<dyn camel_api::IdempotentRepository>,
845 ) -> Result<(), RegistryError> {
846 self.idempotent_repositories.register(name, repo)
847 }
848
849 pub fn idempotent_repository(
851 &self,
852 name: &str,
853 ) -> Option<Arc<dyn camel_api::IdempotentRepository>> {
854 self.idempotent_repositories.get(name)
855 }
856
857 pub fn register_claim_check_repository(
864 &mut self,
865 name: impl Into<String>,
866 repo: Arc<dyn camel_api::ClaimCheckRepository>,
867 ) -> Result<(), RegistryError> {
868 self.claim_check_repositories.register(name, repo)
869 }
870
871 pub fn claim_check_repository(
873 &self,
874 name: &str,
875 ) -> Option<Arc<dyn camel_api::ClaimCheckRepository>> {
876 self.claim_check_repositories.get(name)
877 }
878}
879
880impl ComponentRegistrar for CamelContext {
881 fn register_component_dyn(&mut self, component: Arc<dyn Component>) {
882 let scheme = component.scheme().to_string();
883 self.registry
884 .lock()
885 .expect("mutex poisoned: another thread panicked while holding this lock") .register(component);
887 trace!(scheme, "Registered component");
888 }
889}
890
891impl ComponentContext for CamelContext {
892 fn resolve_component(&self, scheme: &str) -> Option<Arc<dyn Component>> {
893 self.registry.lock().ok()?.get(scheme)
894 }
895
896 fn resolve_language(&self, name: &str) -> Option<Arc<dyn Language>> {
897 self.languages.lock().ok()?.get(name).cloned()
898 }
899
900 fn metrics(&self) -> Arc<dyn MetricsCollector> {
901 Arc::clone(&self.metrics)
902 }
903
904 fn health(&self) -> Arc<dyn camel_component_api::HealthCheckRegistry> {
905 Arc::clone(&self.health_registry) as Arc<dyn camel_component_api::HealthCheckRegistry>
908 }
909
910 fn platform_service(&self) -> Arc<dyn PlatformService> {
911 Arc::clone(&self.platform_service)
912 }
913
914 fn register_route_health_check(
915 &self,
916 route_id: &str,
917 check: Arc<dyn camel_api::AsyncHealthCheck>,
918 ) {
919 self.health_registry.register_for_route(route_id, check);
920 }
921
922 fn unregister_route_health_check(&self, route_id: &str) {
923 self.health_registry.unregister_for_route(route_id);
924 }
925}
926
927#[async_trait::async_trait]
928impl camel_api::HealthSource for CamelContext {
929 async fn liveness(&self) -> camel_api::HealthStatus {
930 let has_failed = self
931 .services
932 .iter()
933 .any(|s| s.status() == camel_api::ServiceStatus::Failed);
934 if has_failed {
935 camel_api::HealthStatus::Unhealthy
936 } else {
937 camel_api::HealthStatus::Healthy
938 }
939 }
940
941 async fn readiness(&self) -> camel_api::HealthStatus {
942 let has_failed = self
943 .services
944 .iter()
945 .any(|s| s.status() == camel_api::ServiceStatus::Failed);
946 if has_failed {
947 return camel_api::HealthStatus::Unhealthy;
948 }
949 let has_stopped = self
950 .services
951 .iter()
952 .any(|s| s.status() == camel_api::ServiceStatus::Stopped);
953 if has_stopped {
954 return camel_api::HealthStatus::Degraded;
955 }
956 self.health_registry.check_all().await.status
957 }
958
959 async fn health_report(&self) -> camel_api::HealthReport {
960 let mut report = self.health_registry.check_all().await;
961 let mut worst = report.status;
962 for service in &self.services {
963 let svc_status = service.status();
964 let health = match svc_status {
965 camel_api::ServiceStatus::Started => camel_api::HealthStatus::Healthy,
966 camel_api::ServiceStatus::Stopped => camel_api::HealthStatus::Degraded,
967 camel_api::ServiceStatus::Failed => camel_api::HealthStatus::Unhealthy,
968 };
969 if matches!(worst, camel_api::HealthStatus::Healthy)
970 && matches!(
971 health,
972 camel_api::HealthStatus::Degraded | camel_api::HealthStatus::Unhealthy
973 )
974 {
975 worst = health;
976 }
977 if matches!(worst, camel_api::HealthStatus::Degraded)
978 && matches!(health, camel_api::HealthStatus::Unhealthy)
979 {
980 worst = health;
981 }
982 report.services.push(camel_api::ServiceHealth {
983 name: service.name().to_string(),
984 status: svc_status,
985 message: None,
986 });
987 }
988 report.status = worst;
989 report
990 }
991
992 async fn startup(&self) -> camel_api::HealthStatus {
993 camel_api::HealthStatus::Healthy
994 }
995}
996
997#[cfg(test)]
998#[path = "context_tests.rs"]
999mod context_tests;