1use std::any::{Any, TypeId};
2use std::collections::HashMap;
3use std::sync::Arc;
4use std::sync::atomic::{AtomicU64, Ordering};
5use tokio_util::sync::CancellationToken;
6use tracing::{debug, trace};
7
8#[cfg(test)]
9use camel_api::StepLifecycle;
10use camel_api::component_metadata::ComponentMetadata;
11use camel_api::error_handler::ErrorHandlerConfig;
12use camel_api::{
13 CamelError, FunctionInvoker, HealthReport, Lifecycle, MetricsCollector, MetricsHandle,
14 PlatformIdentity, PlatformService, ReadinessGate, RouteTemplateSpec, RuntimeCommandBus,
15 RuntimeQueryBus, TemplateInstanceRecord,
16};
17use camel_component_api::{Component, ComponentContext, ComponentRegistrar};
18use camel_language_api::Language;
19
20use crate::health_registry::HealthCheckRegistry;
21use crate::intercept::InterceptRules;
22use crate::language_registry::LanguageRegistryError;
23use crate::lifecycle::adapters::controller_actor::RouteControllerHandle;
24use crate::lifecycle::adapters::route_controller::SharedLanguageRegistry;
25use crate::lifecycle::application::route_definition::RouteDefinition;
26use crate::lifecycle::application::runtime_bus::RuntimeBus;
27use crate::registry::RegistryError;
28use crate::shared::components::domain::Registry;
29use crate::shared::observability::domain::{MetricsLeversConfig, TracerConfig};
30use crate::startup_validation::ConfigCheck;
31use crate::template::TemplateRegistry;
32
33pub use crate::context_builder::CamelContextBuilder;
34
35pub struct CamelContext {
44 registry: Arc<std::sync::Mutex<Registry>>,
45 route_controller: RouteControllerHandle,
46 actor_join: Option<tokio::task::JoinHandle<()>>,
47 supervision_join: Option<tokio::task::JoinHandle<()>>,
48 runtime: Arc<RuntimeBus>,
49 cancel_token: CancellationToken,
50 metrics: Arc<MetricsHandle>,
55 metrics_levers: MetricsLeversConfig,
61 platform_service: Arc<dyn PlatformService>,
63 languages: SharedLanguageRegistry,
64 shutdown_timeout: std::time::Duration,
65 services: Vec<Box<dyn Lifecycle>>,
66 health_registry: Arc<HealthCheckRegistry>,
67 component_configs: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
68 function_invoker: Option<Arc<dyn FunctionInvoker>>,
69 template_registry: Arc<TemplateRegistry>,
70 idempotent_repositories: crate::registry::SharedIdempotentRegistry,
71 claim_check_repositories: crate::registry::SharedClaimCheckRegistry,
72 cache_repositories: crate::registry::SharedCacheRegistry,
73 startup_checks: Vec<Box<dyn ConfigCheck>>,
77 build_version: &'static str,
81 build_git_sha: &'static str,
82 build_started_at: std::time::Instant,
84 in_flight_total: Arc<AtomicU64>,
89}
90
91pub(crate) struct FromParts {
94 pub(crate) registry: Arc<std::sync::Mutex<Registry>>,
95 pub(crate) route_controller: RouteControllerHandle,
96 pub(crate) _actor_join: tokio::task::JoinHandle<()>,
97 pub(crate) supervision_join: Option<tokio::task::JoinHandle<()>>,
98 pub(crate) runtime: Arc<RuntimeBus>,
99 pub(crate) cancel_token: CancellationToken,
100 pub(crate) metrics: Arc<MetricsHandle>,
101 pub(crate) platform_service: Arc<dyn PlatformService>,
102 pub(crate) languages: SharedLanguageRegistry,
103 pub(crate) shutdown_timeout: std::time::Duration,
104 pub(crate) services: Vec<Box<dyn Lifecycle>>,
105 pub(crate) health_registry: Arc<HealthCheckRegistry>,
106 pub(crate) component_configs: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
107 pub(crate) function_invoker: Option<Arc<dyn FunctionInvoker>>,
108 pub(crate) template_registry: Arc<TemplateRegistry>,
109 pub(crate) idempotent_repositories: crate::registry::SharedIdempotentRegistry,
110 pub(crate) claim_check_repositories: crate::registry::SharedClaimCheckRegistry,
111 pub(crate) cache_repositories: crate::registry::SharedCacheRegistry,
112 pub(crate) startup_checks: Vec<Box<dyn ConfigCheck>>,
113 pub(crate) build_version: &'static str,
114 pub(crate) build_git_sha: &'static str,
115 pub(crate) build_started_at: std::time::Instant,
116 pub(crate) in_flight_total: Arc<AtomicU64>,
117}
118
119impl CamelContext {
120 pub(crate) fn from_parts(parts: FromParts) -> Self {
121 Self {
122 registry: parts.registry,
123 route_controller: parts.route_controller,
124 actor_join: Some(parts._actor_join),
125 supervision_join: parts.supervision_join,
126 runtime: parts.runtime,
127 cancel_token: parts.cancel_token,
128 metrics: parts.metrics,
129 metrics_levers: MetricsLeversConfig::default(),
130 platform_service: parts.platform_service,
131 languages: parts.languages,
132 shutdown_timeout: parts.shutdown_timeout,
133 services: parts.services,
134 health_registry: parts.health_registry,
135 component_configs: parts.component_configs,
136 function_invoker: parts.function_invoker,
137 template_registry: parts.template_registry,
138 idempotent_repositories: parts.idempotent_repositories,
139 claim_check_repositories: parts.claim_check_repositories,
140 cache_repositories: parts.cache_repositories,
141 startup_checks: parts.startup_checks,
142 build_version: parts.build_version,
143 build_git_sha: parts.build_git_sha,
144 build_started_at: parts.build_started_at,
145 in_flight_total: parts.in_flight_total,
146 }
147 }
148}
149
150#[derive(Clone)]
154pub struct RuntimeExecutionHandle {
155 pub(crate) controller: RouteControllerHandle,
156 pub(crate) runtime: Arc<RuntimeBus>,
157 pub(crate) function_invoker: Option<Arc<dyn FunctionInvoker>>,
158 #[cfg(test)]
162 #[allow(clippy::type_complexity)]
163 pub(crate) test_lifecycle_inject: Arc<std::sync::Mutex<Option<Vec<Arc<dyn StepLifecycle>>>>>,
164}
165
166impl RuntimeExecutionHandle {
167 pub(crate) async fn add_route_definition(
168 &self,
169 definition: RouteDefinition,
170 ) -> Result<(), CamelError> {
171 use crate::lifecycle::application::ports::RouteRegistrationPort;
172 self.runtime
173 .register_route(definition)
174 .await
175 .map_err(Into::into)
176 }
177
178 #[allow(dead_code)]
181 pub(crate) async fn compile_route_definition(
182 &self,
183 definition: RouteDefinition,
184 ) -> Result<camel_api::BoxProcessor, CamelError> {
185 self.controller.compile_route_definition(definition).await
186 }
187
188 #[allow(dead_code)] pub(crate) async fn compile_route_definition_with_generation(
190 &self,
191 definition: RouteDefinition,
192 generation: u64,
193 ) -> Result<camel_api::BoxProcessor, CamelError> {
194 self.controller
195 .compile_route_definition_with_generation(definition, generation)
196 .await
197 }
198
199 pub(crate) async fn compile_route_definition_pipeline(
200 &self,
201 definition: RouteDefinition,
202 generation: u64,
203 ) -> Result<crate::lifecycle::domain::CompiledPipeline, CamelError> {
204 self.controller
205 .compile_route_definition_pipeline(definition, generation)
206 .await
207 }
208
209 pub(crate) async fn compile_route_definition_dry_pipeline(
212 &self,
213 definition: RouteDefinition,
214 ) -> Result<crate::lifecycle::domain::CompiledPipeline, CamelError> {
215 self.controller
216 .compile_route_definition_dry_pipeline(definition)
217 .await
218 }
219
220 pub(crate) async fn prepare_route_definition_with_generation(
221 &self,
222 definition: RouteDefinition,
223 generation: u64,
224 ) -> Result<crate::lifecycle::domain::route_compilation::PreparedRoute, CamelError> {
225 self.controller
226 .prepare_route_definition_with_generation(definition, generation)
227 .await
228 }
229
230 pub(crate) async fn insert_prepared_route(
231 &self,
232 prepared: crate::lifecycle::domain::route_compilation::PreparedRoute,
233 ) -> Result<(), CamelError> {
234 self.controller.insert_prepared_route(prepared).await
235 }
236
237 pub(crate) async fn discard_prepared_staging(&self, route_id: &str) -> Result<(), CamelError> {
238 self.controller.discard_prepared_staging(route_id).await
239 }
240
241 pub(crate) async fn remove_route_preserving_functions(
242 &self,
243 route_id: String,
244 ) -> Result<(), CamelError> {
245 self.controller
246 .remove_route_preserving_functions(route_id)
247 .await
248 }
249
250 pub(crate) async fn register_route_aggregate(
251 &self,
252 route_id: String,
253 ) -> Result<(), CamelError> {
254 self.runtime.register_aggregate_only(route_id).await
255 }
256
257 pub(crate) async fn swap_route_pipeline(
258 &self,
259 route_id: &str,
260 pipeline: camel_api::BoxProcessor,
261 ) -> Result<(), CamelError> {
262 self.controller.swap_pipeline(route_id, pipeline).await
263 }
264
265 pub(crate) async fn stop_route_reload(&self, route_id: &str) -> Result<(), CamelError> {
267 self.controller.stop_route_reload(route_id).await
268 }
269
270 pub(crate) async fn start_route_reload(&self, route_id: &str) -> Result<(), CamelError> {
272 self.controller.start_route_reload(route_id).await
273 }
274
275 pub(crate) async fn swap_route_pipeline_raw(
278 &self,
279 route_id: &str,
280 pipeline: camel_api::BoxProcessor,
281 lifecycle: Vec<Arc<dyn camel_api::StepLifecycle>>,
282 ) -> Result<(), CamelError> {
283 self.controller
284 .swap_pipeline_raw(route_id, pipeline, lifecycle)
285 .await
286 }
287
288 pub(crate) async fn execute_runtime_command(
289 &self,
290 cmd: camel_api::RuntimeCommand,
291 ) -> Result<camel_api::RuntimeCommandResult, CamelError> {
292 self.runtime.execute(cmd).await
293 }
294
295 pub(crate) async fn runtime_route_status(
296 &self,
297 route_id: &str,
298 ) -> Result<Option<String>, CamelError> {
299 match self
300 .runtime
301 .ask(camel_api::RuntimeQuery::GetRouteStatus {
302 route_id: route_id.to_string(),
303 })
304 .await
305 {
306 Ok(camel_api::RuntimeQueryResult::RouteStatus { status, .. }) => Ok(Some(status)),
307 Ok(_) => Err(CamelError::RouteError(
308 "unexpected runtime query response for route status".to_string(),
309 )),
310 Err(CamelError::RouteError(msg)) if msg.contains("not found") => Ok(None),
311 Err(err) => Err(err),
312 }
313 }
314
315 pub(crate) async fn runtime_route_ids(&self) -> Result<Vec<String>, CamelError> {
316 match self.runtime.ask(camel_api::RuntimeQuery::ListRoutes).await {
317 Ok(camel_api::RuntimeQueryResult::Routes { route_ids }) => Ok(route_ids),
318 Ok(_) => Err(CamelError::RouteError(
319 "unexpected runtime query response for route listing".to_string(),
320 )),
321 Err(err) => Err(err),
322 }
323 }
324
325 pub(crate) async fn route_source_hash(&self, route_id: &str) -> Option<u64> {
326 self.controller.route_source_hash(route_id).await
327 }
328
329 pub(crate) async fn in_flight_count(&self, route_id: &str) -> Result<u64, CamelError> {
330 if !self.controller.route_exists(route_id).await? {
331 return Err(CamelError::RouteError(format!(
332 "Route '{}' not found",
333 route_id
334 )));
335 }
336 Ok(self
337 .controller
338 .in_flight_count(route_id)
339 .await?
340 .unwrap_or(0))
341 }
342
343 pub(crate) async fn route_has_lifecycle(&self, route_id: &str) -> bool {
345 self.controller
346 .route_has_lifecycle(route_id)
347 .await
348 .unwrap_or(false)
349 }
350
351 pub(crate) fn function_invoker(&self) -> Option<Arc<dyn FunctionInvoker>> {
352 self.function_invoker.clone()
353 }
354
355 #[cfg(test)]
356 pub(crate) async fn force_start_route_for_test(
357 &self,
358 route_id: &str,
359 ) -> Result<(), CamelError> {
360 self.controller.start_route(route_id).await
361 }
362
363 pub async fn controller_route_count_for_test(&self) -> usize {
364 self.controller.route_count().await.unwrap_or(0)
365 }
366}
367
368#[async_trait::async_trait]
369impl crate::hot_reload::ports::ReloadExecutorPort for RuntimeExecutionHandle {
370 async fn add_route_definition(&self, definition: RouteDefinition) -> Result<(), CamelError> {
371 RuntimeExecutionHandle::add_route_definition(self, definition).await
372 }
373
374 async fn compile_route_definition_pipeline(
375 &self,
376 definition: RouteDefinition,
377 generation: u64,
378 ) -> Result<crate::lifecycle::domain::CompiledPipeline, CamelError> {
379 RuntimeExecutionHandle::compile_route_definition_pipeline(self, definition, generation)
380 .await
381 }
382
383 async fn compile_route_definition_dry_pipeline(
384 &self,
385 definition: RouteDefinition,
386 ) -> Result<crate::lifecycle::domain::CompiledPipeline, CamelError> {
387 RuntimeExecutionHandle::compile_route_definition_dry_pipeline(self, definition).await
388 }
389
390 async fn prepare_route_definition_with_generation(
391 &self,
392 definition: RouteDefinition,
393 generation: u64,
394 ) -> Result<crate::lifecycle::domain::route_compilation::PreparedRoute, CamelError> {
395 RuntimeExecutionHandle::prepare_route_definition_with_generation(
396 self, definition, generation,
397 )
398 .await
399 }
400
401 async fn insert_prepared_route(
402 &self,
403 prepared: crate::lifecycle::domain::route_compilation::PreparedRoute,
404 ) -> Result<(), CamelError> {
405 RuntimeExecutionHandle::insert_prepared_route(self, prepared).await
406 }
407
408 async fn discard_prepared_staging(&self, route_id: &str) -> Result<(), CamelError> {
409 RuntimeExecutionHandle::discard_prepared_staging(self, route_id).await
410 }
411
412 async fn remove_route_preserving_functions(&self, route_id: String) -> Result<(), CamelError> {
413 RuntimeExecutionHandle::remove_route_preserving_functions(self, route_id).await
414 }
415
416 async fn register_route_aggregate(&self, route_id: String) -> Result<(), CamelError> {
417 RuntimeExecutionHandle::register_route_aggregate(self, route_id).await
418 }
419
420 async fn swap_route_pipeline(
421 &self,
422 route_id: &str,
423 pipeline: camel_api::BoxProcessor,
424 ) -> Result<(), CamelError> {
425 RuntimeExecutionHandle::swap_route_pipeline(self, route_id, pipeline).await
426 }
427
428 async fn stop_route_reload(&self, route_id: &str) -> Result<(), CamelError> {
429 RuntimeExecutionHandle::stop_route_reload(self, route_id).await
430 }
431
432 async fn start_route_reload(&self, route_id: &str) -> Result<(), CamelError> {
433 RuntimeExecutionHandle::start_route_reload(self, route_id).await
434 }
435
436 async fn swap_route_pipeline_raw(
437 &self,
438 route_id: &str,
439 pipeline: camel_api::BoxProcessor,
440 lifecycle: Vec<std::sync::Arc<dyn camel_api::StepLifecycle>>,
441 ) -> Result<(), CamelError> {
442 RuntimeExecutionHandle::swap_route_pipeline_raw(self, route_id, pipeline, lifecycle).await
443 }
444
445 async fn execute_runtime_command(
446 &self,
447 cmd: camel_api::RuntimeCommand,
448 ) -> Result<camel_api::RuntimeCommandResult, CamelError> {
449 RuntimeExecutionHandle::execute_runtime_command(self, cmd).await
450 }
451
452 async fn runtime_route_status(&self, route_id: &str) -> Result<Option<String>, CamelError> {
453 RuntimeExecutionHandle::runtime_route_status(self, route_id).await
454 }
455
456 async fn in_flight_count(&self, route_id: &str) -> Result<u64, CamelError> {
457 RuntimeExecutionHandle::in_flight_count(self, route_id).await
458 }
459
460 async fn route_has_lifecycle(&self, route_id: &str) -> bool {
461 RuntimeExecutionHandle::route_has_lifecycle(self, route_id).await
462 }
463
464 #[cfg(test)]
465 fn take_test_lifecycle_inject(
466 &self,
467 ) -> Option<Vec<std::sync::Arc<dyn camel_api::StepLifecycle>>> {
468 self.test_lifecycle_inject.lock().unwrap().take()
469 }
470}
471
472impl CamelContext {
473 pub fn builder() -> CamelContextBuilder {
474 CamelContextBuilder::new()
475 }
476
477 pub async fn set_error_handler(&mut self, config: ErrorHandlerConfig) {
479 let _ = self.route_controller.set_error_handler(config).await;
480 }
481
482 pub async fn set_bind_exposure_acks(
486 &mut self,
487 acks: crate::lifecycle::adapters::route_controller_trait::BindExposureAcks,
488 ) {
489 let _ = self.route_controller.set_bind_exposure_acks(acks).await;
490 }
491
492 pub async fn set_tracing(&mut self, enabled: bool) {
494 let config = TracerConfig {
495 enabled,
496 ..Default::default()
497 };
498 self.metrics_levers = config.metrics_levers.clone();
501 let _ = self.route_controller.set_tracer_config(config).await;
502 }
503
504 pub async fn set_tracer_config(&mut self, config: TracerConfig) {
506 self.metrics_levers = config.metrics_levers.clone();
509 let _ = self.route_controller.set_tracer_config(config).await;
510 }
511
512 pub async fn with_tracing(mut self) -> Self {
514 self.set_tracing(true).await;
515 self
516 }
517
518 pub async fn with_tracer_config(mut self, config: TracerConfig) -> Self {
522 self.set_tracer_config(config).await;
523 self
524 }
525
526 pub fn with_lifecycle<L: Lifecycle + 'static>(mut self, service: L) -> Self {
535 self.add_lifecycle(service);
536 self
537 }
538
539 pub fn add_lifecycle<L: Lifecycle + 'static>(&mut self, service: L) {
544 if let Some(collector) = service.as_metrics_collector() {
545 self.metrics.register(collector);
549 self.metrics
554 .record_build_info(self.build_version, self.build_git_sha);
555 self.metrics
556 .record_uptime(self.build_started_at.elapsed().as_secs_f64());
557 }
558 if let Some(invoker) = service.as_function_invoker() {
559 self.function_invoker = Some(invoker.clone());
560 if let Err(e) = self.route_controller.try_set_function_invoker(invoker) {
561 tracing::debug!("Failed to propagate function invoker to route controller: {e}");
562 }
563 }
564
565 self.services.push(Box::new(service));
566 }
567
568 pub fn register_component<C: Component + 'static>(&mut self, component: C) {
574 self.register_component_dyn(Arc::new(component));
575 }
576
577 pub async fn set_intercept_rules(&self, rules: InterceptRules) -> Result<(), CamelError> {
584 self.route_controller.set_intercept_rules(rules).await
585 }
586
587 pub fn add_startup_check(&mut self, check: Box<dyn ConfigCheck>) {
596 self.startup_checks.push(check);
597 }
598
599 pub fn register_language(
606 &mut self,
607 name: impl Into<String>,
608 lang: Box<dyn Language>,
609 ) -> Result<(), LanguageRegistryError> {
610 let name = name.into();
611 let mut languages = self
612 .languages
613 .lock()
614 .expect("mutex poisoned: another thread panicked while holding this lock"); if languages.contains_key(&name) {
616 return Err(LanguageRegistryError::AlreadyRegistered { name });
617 }
618 languages.insert(name, Arc::from(lang));
619 Ok(())
620 }
621
622 pub fn resolve_language(&self, name: &str) -> Option<Arc<dyn Language>> {
624 let languages = self
625 .languages
626 .lock()
627 .expect("mutex poisoned: another thread panicked while holding this lock"); languages.get(name).cloned()
629 }
630
631 pub async fn add_route_definition(
635 &self,
636 definition: RouteDefinition,
637 ) -> Result<(), CamelError> {
638 use crate::lifecycle::application::ports::RouteRegistrationPort;
639 debug!(
640 from = definition.from_uri(),
641 route_id = %definition.route_id(),
642 "Adding route definition"
643 );
644 self.runtime
645 .register_route(definition)
646 .await
647 .map_err(Into::into)
648 }
649
650 pub fn registry(&self) -> std::sync::MutexGuard<'_, Registry> {
652 self.registry
653 .lock()
654 .expect("mutex poisoned: another thread panicked while holding this lock") }
656
657 pub fn registry_arc(&self) -> Arc<std::sync::Mutex<Registry>> {
659 Arc::clone(&self.registry)
660 }
661
662 pub fn runtime_execution_handle(&self) -> RuntimeExecutionHandle {
664 RuntimeExecutionHandle {
665 controller: self.route_controller.clone(),
666 runtime: Arc::clone(&self.runtime),
667 function_invoker: self.function_invoker.clone(),
668 #[cfg(test)]
669 test_lifecycle_inject: Arc::new(std::sync::Mutex::new(None)),
670 }
671 }
672
673 pub fn metrics(&self) -> Arc<dyn MetricsCollector> {
675 Arc::clone(&self.metrics) as Arc<dyn MetricsCollector>
676 }
677
678 pub fn total_in_flight(&self) -> u64 {
688 self.in_flight_total.load(Ordering::Acquire)
689 }
690
691 pub fn platform_service(&self) -> Arc<dyn PlatformService> {
693 Arc::clone(&self.platform_service)
694 }
695
696 pub fn readiness_gate(&self) -> Arc<dyn ReadinessGate> {
698 self.platform_service.readiness_gate()
699 }
700
701 pub fn platform_identity(&self) -> PlatformIdentity {
703 self.platform_service.identity()
704 }
705
706 pub fn leadership(&self) -> Arc<dyn camel_api::LeadershipService> {
708 self.platform_service.leadership()
709 }
710
711 pub fn runtime(&self) -> Arc<dyn camel_api::RuntimeHandle> {
713 self.runtime.clone()
714 }
715
716 pub fn producer_context(&self) -> camel_api::ProducerContext {
718 camel_api::ProducerContext::new().with_runtime(self.runtime())
719 }
720
721 pub async fn runtime_route_status(&self, route_id: &str) -> Result<Option<String>, CamelError> {
723 match self
724 .runtime()
725 .ask(camel_api::RuntimeQuery::GetRouteStatus {
726 route_id: route_id.to_string(),
727 })
728 .await
729 {
730 Ok(camel_api::RuntimeQueryResult::RouteStatus { status, .. }) => Ok(Some(status)),
731 Ok(_) => Err(CamelError::RouteError(
732 "unexpected runtime query response for route status".to_string(),
733 )),
734 Err(CamelError::RouteError(msg)) if msg.contains("not found") => Ok(None),
735 Err(err) => Err(err),
736 }
737 }
738
739 pub async fn start(&mut self) -> Result<(), CamelError> {
747 crate::lifecycle::application::context_lifecycle::start_context(
748 &mut self.services,
749 &mut self.startup_checks,
750 &self.runtime,
751 &self.route_controller,
752 &mut self.cancel_token,
753 )
754 .await?;
755 self.route_controller.mark_started().await
759 }
760
761 pub async fn stop(&mut self) -> Result<(), CamelError> {
763 self.stop_timeout(self.shutdown_timeout).await
764 }
765
766 pub async fn stop_timeout(&mut self, _timeout: std::time::Duration) -> Result<(), CamelError> {
776 crate::lifecycle::application::context_lifecycle::stop_context(
777 &self.cancel_token,
778 &mut self.supervision_join,
779 &self.runtime,
780 &self.route_controller,
781 &mut self.services,
782 )
783 .await
784 }
785
786 pub fn shutdown_timeout(&self) -> std::time::Duration {
788 self.shutdown_timeout
789 }
790
791 pub fn set_shutdown_timeout(&mut self, timeout: std::time::Duration) {
793 self.shutdown_timeout = timeout;
794 }
795
796 #[cfg(test)]
799 pub(crate) fn take_actor_join(&mut self) -> Option<tokio::task::JoinHandle<()>> {
800 self.actor_join.take()
801 }
802
803 pub async fn abort(&mut self) {
811 crate::lifecycle::application::context_lifecycle::abort_context(
812 &self.cancel_token,
813 &mut self.supervision_join,
814 &self.runtime,
815 &self.route_controller as &dyn crate::lifecycle::application::ports::RouteOrderingPort,
816 &self.route_controller
817 as &dyn crate::lifecycle::application::ports::RouteDestructiveTeardownPort,
818 &mut self.services,
819 self.health_registry.cancel_token(),
820 &mut self.actor_join,
821 )
822 .await
823 }
824
825 pub async fn health_check(&self) -> HealthReport {
827 use camel_api::HealthSource;
828 self.health_report().await
829 }
830
831 pub fn health_registry(&self) -> Arc<HealthCheckRegistry> {
832 Arc::clone(&self.health_registry)
833 }
834
835 pub fn set_component_config<T: 'static + Send + Sync>(&mut self, config: T) {
837 self.component_configs
838 .insert(TypeId::of::<T>(), Box::new(config));
839 }
840
841 pub fn get_component_config<T: 'static + Send + Sync>(&self) -> Option<&T> {
843 self.component_configs
844 .get(&TypeId::of::<T>())
845 .and_then(|b| b.downcast_ref::<T>())
846 }
847
848 pub fn component_metadata(&self, scheme: &str) -> Option<ComponentMetadata> {
852 self.registry.lock().ok()?.get_metadata(scheme)
853 }
854
855 pub fn all_component_metadata(&self) -> Vec<ComponentMetadata> {
857 self.registry
858 .lock()
859 .expect("mutex poisoned: another thread panicked while holding this lock") .all_metadata()
861 }
862
863 pub fn metadata_catalog(
870 &self,
871 ) -> crate::component_metadata_catalog::RuntimeComponentMetadataCatalog {
872 crate::component_metadata_catalog::RuntimeComponentMetadataCatalog::new(Arc::clone(
873 &self.registry,
874 ))
875 }
876
877 pub fn add_route_template(&self, spec: RouteTemplateSpec) -> Result<(), CamelError> {
883 self.template_registry.register(spec)
884 }
885
886 pub fn get_route_template(&self, id: &str) -> Option<RouteTemplateSpec> {
888 self.template_registry.get(id)
889 }
890
891 pub fn template_ids(&self) -> Vec<String> {
893 self.template_registry.template_ids()
894 }
895
896 pub fn record_template_instance(&self, record: TemplateInstanceRecord) {
898 self.template_registry.record_instance(record)
899 }
900
901 pub fn template_instances(&self, template_id: &str) -> Vec<TemplateInstanceRecord> {
903 self.template_registry.instances(template_id)
904 }
905
906 pub fn register_idempotent_repository(
913 &mut self,
914 name: impl Into<String>,
915 repo: Arc<dyn camel_api::IdempotentRepository>,
916 ) -> Result<(), RegistryError> {
917 self.idempotent_repositories.register(name, repo)
918 }
919
920 pub fn idempotent_repository(
922 &self,
923 name: &str,
924 ) -> Option<Arc<dyn camel_api::IdempotentRepository>> {
925 self.idempotent_repositories.get(name)
926 }
927
928 pub fn register_claim_check_repository(
935 &mut self,
936 name: impl Into<String>,
937 repo: Arc<dyn camel_api::ClaimCheckRepository>,
938 ) -> Result<(), RegistryError> {
939 self.claim_check_repositories.register(name, repo)
940 }
941
942 pub fn claim_check_repository(
944 &self,
945 name: &str,
946 ) -> Option<Arc<dyn camel_api::ClaimCheckRepository>> {
947 self.claim_check_repositories.get(name)
948 }
949
950 pub fn register_cache_repository(
957 &mut self,
958 name: impl Into<String>,
959 repo: Arc<dyn camel_api::CacheRepository>,
960 ) -> Result<(), RegistryError> {
961 self.cache_repositories.register(name, repo)
962 }
963
964 pub fn replace_cache_repository(
968 &mut self,
969 name: impl Into<String>,
970 repo: Arc<dyn camel_api::CacheRepository>,
971 ) -> Option<Arc<dyn camel_api::CacheRepository>> {
972 self.cache_repositories.register_or_replace(name, repo)
973 }
974
975 pub fn cache_repository(&self, name: &str) -> Option<Arc<dyn camel_api::CacheRepository>> {
977 self.cache_repositories.get(name)
978 }
979
980 pub fn shutdown_token(&self) -> CancellationToken {
985 self.cancel_token.clone()
986 }
987}
988
989impl ComponentRegistrar for CamelContext {
990 fn register_component_dyn(&mut self, component: Arc<dyn Component>) {
991 let scheme = component.scheme().to_string();
992 self.registry
993 .lock()
994 .expect("mutex poisoned: another thread panicked while holding this lock") .register(component);
996 trace!(scheme, "Registered component");
997 }
998}
999
1000impl ComponentContext for CamelContext {
1001 fn resolve_component(&self, scheme: &str) -> Option<Arc<dyn Component>> {
1002 self.registry.lock().ok()?.get(scheme)
1003 }
1004
1005 fn resolve_language(&self, name: &str) -> Option<Arc<dyn Language>> {
1006 self.languages.lock().ok()?.get(name).cloned()
1007 }
1008
1009 fn metrics(&self) -> Arc<dyn MetricsCollector> {
1010 Arc::clone(&self.metrics) as Arc<dyn MetricsCollector>
1011 }
1012
1013 fn component_metrics_enabled(&self) -> bool {
1019 self.metrics_levers.components_enabled()
1020 }
1021
1022 fn health(&self) -> Arc<dyn camel_component_api::HealthCheckRegistry> {
1023 Arc::clone(&self.health_registry) as Arc<dyn camel_component_api::HealthCheckRegistry>
1026 }
1027
1028 fn platform_service(&self) -> Arc<dyn PlatformService> {
1029 Arc::clone(&self.platform_service)
1030 }
1031
1032 fn register_route_health_check(
1033 &self,
1034 route_id: &str,
1035 check: Arc<dyn camel_api::AsyncHealthCheck>,
1036 ) {
1037 self.health_registry.register_for_route(route_id, check);
1038 }
1039
1040 fn unregister_route_health_check(&self, route_id: &str) {
1041 self.health_registry.unregister_for_route(route_id);
1042 }
1043
1044 fn in_flight_counter(&self) -> Option<Arc<AtomicU64>> {
1048 Some(Arc::clone(&self.in_flight_total))
1049 }
1050}
1051
1052#[async_trait::async_trait]
1053impl camel_api::HealthSource for CamelContext {
1054 async fn liveness(&self) -> camel_api::HealthStatus {
1055 let has_failed = self
1056 .services
1057 .iter()
1058 .any(|s| s.status() == camel_api::ServiceStatus::Failed);
1059 if has_failed {
1060 camel_api::HealthStatus::Unhealthy
1061 } else {
1062 camel_api::HealthStatus::Healthy
1063 }
1064 }
1065
1066 async fn readiness(&self) -> camel_api::HealthStatus {
1067 let has_failed = self
1068 .services
1069 .iter()
1070 .any(|s| s.status() == camel_api::ServiceStatus::Failed);
1071 if has_failed {
1072 return camel_api::HealthStatus::Unhealthy;
1073 }
1074 let has_stopped = self
1075 .services
1076 .iter()
1077 .any(|s| s.status() == camel_api::ServiceStatus::Stopped);
1078 if has_stopped {
1079 return camel_api::HealthStatus::Degraded;
1080 }
1081 self.health_registry.check_all().await.status
1082 }
1083
1084 async fn health_report(&self) -> camel_api::HealthReport {
1085 let mut report = self.health_registry.check_all().await;
1086 let mut worst = report.status;
1087 for service in &self.services {
1088 let svc_status = service.status();
1089 let health = match svc_status {
1090 camel_api::ServiceStatus::Started => camel_api::HealthStatus::Healthy,
1091 camel_api::ServiceStatus::Stopped => camel_api::HealthStatus::Degraded,
1092 camel_api::ServiceStatus::Failed => camel_api::HealthStatus::Unhealthy,
1093 _ => camel_api::HealthStatus::Unhealthy,
1096 };
1097 if matches!(worst, camel_api::HealthStatus::Healthy)
1098 && matches!(
1099 health,
1100 camel_api::HealthStatus::Degraded | camel_api::HealthStatus::Unhealthy
1101 )
1102 {
1103 worst = health;
1104 }
1105 if matches!(worst, camel_api::HealthStatus::Degraded)
1106 && matches!(health, camel_api::HealthStatus::Unhealthy)
1107 {
1108 worst = health;
1109 }
1110 report.services.push(camel_api::ServiceHealth {
1111 name: service.name().to_string(),
1112 status: svc_status,
1113 message: None,
1114 });
1115 }
1116 report.status = worst;
1117 report
1118 }
1119
1120 async fn startup(&self) -> camel_api::HealthStatus {
1121 camel_api::HealthStatus::Healthy
1122 }
1123}
1124
1125#[cfg(test)]
1126#[path = "context_tests.rs"]
1127mod context_tests;