1use std::collections::HashMap;
2use std::sync::Arc;
3use tokio_util::sync::CancellationToken;
4
5use camel_api::{
6 CamelError, FunctionInvoker, InFlightGauge, Lifecycle, MetricsCollector, MetricsHandle,
7 NoopPlatformService, PlatformService, SupervisionConfig,
8};
9use camel_language_api::Language;
10
11use super::context::{CamelContext, FromParts};
12use crate::cache::memory::MemoryCacheRepository;
13use crate::claim_check::memory_repository::MemoryClaimCheckRepository;
14use crate::health_registry::HealthCheckRegistry;
15use crate::idempotent::memory_repository::MemoryIdempotentRepository;
16use crate::intercept::InterceptRules;
17use crate::lifecycle::adapters::RuntimeExecutionAdapter;
18use crate::lifecycle::adapters::controller_actor::{
19 RouteControllerHandle, spawn_controller_actor, spawn_supervision_task,
20};
21use crate::lifecycle::adapters::route_controller::{
22 DefaultRouteController, SharedLanguageRegistry,
23};
24use crate::lifecycle::application::ports::RuntimeExecutionPort;
25use crate::lifecycle::application::runtime_bus::RuntimeBus;
26use crate::registry::{CacheRegistry, ClaimCheckRegistry, IdempotentRegistry};
27use crate::shared::components::domain::Registry;
28use crate::startup_validation::ConfigCheck;
29use crate::template::TemplateRegistry;
30
31type ExecutionFactory =
32 Arc<dyn Fn(RouteControllerHandle) -> Arc<dyn RuntimeExecutionPort> + Send + Sync>;
33
34pub struct CamelContextBuilder {
35 registry: Option<Arc<std::sync::Mutex<Registry>>>,
36 languages: Option<SharedLanguageRegistry>,
37 metrics: Option<Arc<dyn MetricsCollector>>,
38 platform_service: Option<Arc<dyn PlatformService>>,
40 supervision_config: Option<SupervisionConfig>,
41 runtime_store: Option<crate::lifecycle::adapters::InMemoryRuntimeStore>,
42 shutdown_timeout: std::time::Duration,
43 beans: Option<Arc<std::sync::Mutex<camel_bean::BeanRegistry>>>,
44 function_invoker: Option<Arc<dyn FunctionInvoker>>,
45 lifecycle_services: Vec<Box<dyn Lifecycle>>,
46 execution_factory: Option<ExecutionFactory>,
47 health_registry: Option<Arc<HealthCheckRegistry>>,
48 template_registry: Option<Arc<TemplateRegistry>>,
49 intercept_rules: Option<InterceptRules>,
50}
51
52fn spawn_uptime_refresh(
60 metrics: Arc<dyn MetricsCollector>,
61 shutdown: CancellationToken,
62 started: std::time::Instant,
63) {
64 tokio::spawn(async move {
65 let mut interval = tokio::time::interval(std::time::Duration::from_secs(60));
68 interval.tick().await;
69 loop {
70 tokio::select! {
71 _ = shutdown.cancelled() => break,
72 _ = interval.tick() => {
73 metrics.record_uptime(started.elapsed().as_secs_f64());
74 }
75 }
76 }
77 });
78}
79
80impl CamelContextBuilder {
81 pub fn new() -> Self {
82 Self {
83 registry: None,
84 languages: None,
85 metrics: None,
86 platform_service: None,
87 supervision_config: None,
88 runtime_store: None,
89 shutdown_timeout: std::time::Duration::from_secs(5),
90 beans: None,
91 function_invoker: None,
92 lifecycle_services: Vec::new(),
93 execution_factory: None,
94 health_registry: None,
95 template_registry: None,
96 intercept_rules: None,
97 }
98 }
99
100 pub fn registry(mut self, registry: Arc<std::sync::Mutex<Registry>>) -> Self {
101 self.registry = Some(registry);
102 self
103 }
104
105 pub fn languages(mut self, languages: SharedLanguageRegistry) -> Self {
106 self.languages = Some(languages);
107 self
108 }
109
110 pub fn with_execution_factory(
111 mut self,
112 factory: impl Fn(RouteControllerHandle) -> Arc<dyn RuntimeExecutionPort> + Send + Sync + 'static,
113 ) -> Self {
114 self.execution_factory = Some(Arc::new(factory));
115 self
116 }
117
118 pub fn metrics(mut self, metrics: Arc<dyn MetricsCollector>) -> Self {
119 self.metrics = Some(metrics);
120 self
121 }
122
123 pub fn platform_service(mut self, platform_service: Arc<dyn PlatformService>) -> Self {
125 self.platform_service = Some(platform_service);
126 self
127 }
128
129 pub fn supervision(mut self, config: SupervisionConfig) -> Self {
130 self.supervision_config = Some(config);
131 self
132 }
133
134 pub fn runtime_store(
135 mut self,
136 store: crate::lifecycle::adapters::InMemoryRuntimeStore,
137 ) -> Self {
138 self.runtime_store = Some(store);
139 self
140 }
141
142 pub fn shutdown_timeout(mut self, timeout: std::time::Duration) -> Self {
143 self.shutdown_timeout = timeout;
144 self
145 }
146
147 pub fn health_registry(mut self, registry: Arc<HealthCheckRegistry>) -> Self {
148 self.health_registry = Some(registry);
149 self
150 }
151
152 pub fn beans(mut self, beans: Arc<std::sync::Mutex<camel_bean::BeanRegistry>>) -> Self {
154 self.beans = Some(beans);
155 self
156 }
157
158 pub fn with_lifecycle<L: Lifecycle + 'static>(mut self, service: L) -> Self {
164 if let Some(collector) = service.as_metrics_collector() {
165 self.metrics = Some(collector);
166 }
167 if let Some(invoker) = service.as_function_invoker() {
168 self.function_invoker = Some(invoker);
169 }
170 self.lifecycle_services.push(Box::new(service));
171 self
172 }
173
174 pub fn template_registry(mut self, registry: Arc<TemplateRegistry>) -> Self {
178 self.template_registry = Some(registry);
179 self
180 }
181
182 pub fn with_intercept_rules(mut self, rules: InterceptRules) -> Self {
187 self.intercept_rules = Some(rules);
188 self
189 }
190
191 fn built_in_languages() -> SharedLanguageRegistry {
192 crate::language_registry::from_config(&camel_language_api::LanguagesConfig::default())
193 }
194
195 fn build_runtime(
196 controller: RouteControllerHandle,
197 store: crate::lifecycle::adapters::InMemoryRuntimeStore,
198 execution_factory: Option<ExecutionFactory>,
199 health_registry: Arc<HealthCheckRegistry>,
200 metrics: Arc<dyn MetricsCollector>,
201 ) -> Arc<RuntimeBus> {
202 let execution: Arc<dyn RuntimeExecutionPort> = if let Some(factory) = execution_factory {
203 factory(controller.clone())
204 } else {
205 Arc::new(RuntimeExecutionAdapter::new(controller))
206 };
207 let store = store.with_metrics(Arc::clone(&metrics));
213 Arc::new(
214 RuntimeBus::new(
215 Arc::new(store.clone()),
216 Arc::new(store.clone()),
217 Arc::new(store.clone()),
218 Arc::new(store.clone()),
219 )
220 .with_uow(Arc::new(store))
221 .with_execution(execution)
222 .with_health_registry(health_registry)
223 .with_metrics(metrics),
224 )
225 }
226
227 pub async fn build(self) -> Result<CamelContext, CamelError> {
228 let registry = self
229 .registry
230 .unwrap_or_else(|| Arc::new(std::sync::Mutex::new(Registry::new())));
231 let languages = self.languages.unwrap_or_else(Self::built_in_languages);
232 let simple_with_resolver: Arc<dyn Language> = Arc::new(
233 camel_language_simple::SimpleLanguage::with_resolver(Arc::new({
234 let languages = Arc::clone(&languages);
235 move |name| {
236 languages
237 .lock()
238 .ok()
239 .and_then(|registry| registry.get(name).cloned())
240 }
241 })),
242 );
243 languages
244 .lock()
245 .expect("mutex poisoned: another thread panicked while holding this lock") .insert("simple".to_string(), simple_with_resolver);
247 let metrics_handle = Arc::new(MetricsHandle::new());
254 if let Some(collector) = self.metrics {
255 metrics_handle.register(collector);
256 }
257 metrics_handle.record_build_info(
263 env!("CARGO_PKG_VERSION"),
264 option_env!("VERGEN_GIT_SHA").unwrap_or("unknown"),
265 );
266 let started_at = std::time::Instant::now();
267 let cancel_token = CancellationToken::new();
268 let shutdown_token_slot = Arc::new(std::sync::Mutex::new(cancel_token.clone()));
274 spawn_uptime_refresh(
275 Arc::clone(&metrics_handle) as Arc<dyn MetricsCollector>,
276 cancel_token.clone(),
277 started_at,
278 );
279 let platform_service = self
280 .platform_service
281 .unwrap_or_else(|| Arc::new(NoopPlatformService::default()));
282 let health_registry = self.health_registry.unwrap_or_else(|| {
283 Arc::new(HealthCheckRegistry::new(std::time::Duration::from_secs(5)))
284 });
285
286 let idempotent_repositories: crate::registry::SharedIdempotentRegistry = {
291 let reg = Arc::new(IdempotentRegistry::new());
292 let memory = Arc::new(MemoryIdempotentRepository::new("memory"));
293 reg.register("memory", memory)
296 .expect("built-in memory idempotent repository registration must succeed"); reg
298 };
299
300 let claim_check_repositories: crate::registry::SharedClaimCheckRegistry = {
302 let reg = Arc::new(ClaimCheckRegistry::new());
303 let memory = Arc::new(MemoryClaimCheckRepository::new("memory"));
304 reg.register("memory", memory)
305 .expect("built-in memory claim check repository registration must succeed"); reg
307 };
308
309 let cache_repositories: crate::registry::SharedCacheRegistry = {
311 let reg = Arc::new(CacheRegistry::new());
312 let memory = Arc::new(MemoryCacheRepository::new("memory", 10_000));
313 reg.register("memory", memory)
314 .expect("built-in memory cache repository registration must succeed"); reg
316 };
317
318 let in_flight_total = Arc::new(InFlightGauge::new());
324
325 let (controller, actor_join, supervision_join) =
326 if let Some(config) = self.supervision_config {
327 let (crash_tx, crash_rx) = tokio::sync::mpsc::channel(64);
328 let mut controller_impl = if let Some(ref beans) = self.beans {
329 DefaultRouteController::with_languages_and_beans(
330 Arc::clone(®istry),
331 Arc::clone(&languages),
332 Arc::clone(&platform_service),
333 Arc::clone(beans),
334 )
335 } else {
336 DefaultRouteController::with_languages(
337 Arc::clone(®istry),
338 Arc::clone(&languages),
339 Arc::clone(&platform_service),
340 )
341 };
342 if let Some(invoker) = self.function_invoker.clone() {
343 controller_impl = controller_impl.with_function_invoker(invoker);
344 }
345 if let Some(rules) = self.intercept_rules.clone() {
346 controller_impl = controller_impl.with_intercept_rules(rules);
347 }
348 controller_impl.set_idempotent_repositories(Arc::clone(&idempotent_repositories));
349 controller_impl.set_claim_check_repositories(Arc::clone(&claim_check_repositories));
350 controller_impl.set_cache_repositories(Arc::clone(&cache_repositories));
351 controller_impl.set_health_registry(Arc::clone(&health_registry));
352 controller_impl
353 .set_tracer_metrics(Arc::clone(&metrics_handle) as Arc<dyn MetricsCollector>);
354 controller_impl.set_in_flight_total(Arc::clone(&in_flight_total));
355 controller_impl.set_crash_notifier(crash_tx);
356 let (controller, actor_join) = spawn_controller_actor(controller_impl);
357 let supervision_join = spawn_supervision_task(
358 controller.clone(),
359 config,
360 Some(Arc::clone(&metrics_handle) as Arc<dyn MetricsCollector>),
361 crash_rx,
362 );
363 (controller, actor_join, Some(supervision_join))
364 } else {
365 let mut controller_impl = if let Some(ref beans) = self.beans {
366 DefaultRouteController::with_languages_and_beans(
367 Arc::clone(®istry),
368 Arc::clone(&languages),
369 Arc::clone(&platform_service),
370 Arc::clone(beans),
371 )
372 } else {
373 DefaultRouteController::with_languages(
374 Arc::clone(®istry),
375 Arc::clone(&languages),
376 Arc::clone(&platform_service),
377 )
378 };
379 if let Some(invoker) = self.function_invoker.clone() {
380 controller_impl = controller_impl.with_function_invoker(invoker);
381 }
382 if let Some(rules) = self.intercept_rules.clone() {
383 controller_impl = controller_impl.with_intercept_rules(rules);
384 }
385 controller_impl.set_idempotent_repositories(Arc::clone(&idempotent_repositories));
386 controller_impl.set_claim_check_repositories(Arc::clone(&claim_check_repositories));
387 controller_impl.set_cache_repositories(Arc::clone(&cache_repositories));
388 controller_impl.set_health_registry(Arc::clone(&health_registry));
389 controller_impl
390 .set_tracer_metrics(Arc::clone(&metrics_handle) as Arc<dyn MetricsCollector>);
391 controller_impl.set_in_flight_total(Arc::clone(&in_flight_total));
392 let (controller, actor_join) = spawn_controller_actor(controller_impl);
393 (controller, actor_join, None)
394 };
395
396 let store = self.runtime_store.unwrap_or_default();
397 let runtime = Self::build_runtime(
398 controller.clone(),
399 store,
400 self.execution_factory,
401 Arc::clone(&health_registry),
402 Arc::clone(&metrics_handle) as Arc<dyn MetricsCollector>,
403 );
404 let runtime_handle: Arc<dyn camel_api::RuntimeHandle> = runtime.clone();
405 controller
406 .try_set_runtime_handle(runtime_handle)
407 .expect("controller actor mailbox should accept initial runtime handle"); let template_registry = self
410 .template_registry
411 .unwrap_or_else(|| Arc::new(TemplateRegistry::new()));
412
413 Ok(CamelContext::from_parts(FromParts {
414 registry,
415 route_controller: controller,
416 _actor_join: actor_join,
417 supervision_join,
418 runtime,
419 cancel_token,
420 shutdown_token_slot,
421 metrics: metrics_handle,
422 platform_service,
423 languages,
424 shutdown_timeout: self.shutdown_timeout,
425 services: self.lifecycle_services,
426 health_registry,
427 component_configs: HashMap::new(),
428 function_invoker: self.function_invoker,
429 template_registry,
430 idempotent_repositories,
431 claim_check_repositories,
432 cache_repositories,
433 startup_checks: Vec::<Box<dyn ConfigCheck>>::new(),
434 build_version: env!("CARGO_PKG_VERSION"),
435 build_git_sha: option_env!("VERGEN_GIT_SHA").unwrap_or("unknown"),
436 build_started_at: started_at,
437 in_flight_total,
438 }))
439 }
440}
441
442impl Default for CamelContextBuilder {
443 fn default() -> Self {
444 Self::new()
445 }
446}
447
448#[cfg(test)]
449mod tests {
450 use super::*;
451
452 #[test]
453 fn builder_default_has_sane_timeout() {
454 let builder = CamelContextBuilder::new();
455 assert_eq!(builder.shutdown_timeout, std::time::Duration::from_secs(5));
456 }
457
458 #[tokio::test]
459 async fn builder_registers_default_memory_idempotent_repository() {
460 let ctx = CamelContext::builder()
461 .build()
462 .await
463 .expect("build context");
464 let repo = ctx.idempotent_repository("memory");
465 assert!(
466 repo.is_some(),
467 "default 'memory' idempotent repository should be registered"
468 );
469 }
470
471 #[tokio::test]
472 async fn builder_registers_default_memory_cache_repository() {
473 let ctx = CamelContext::builder()
474 .build()
475 .await
476 .expect("build context");
477 let repo = ctx.cache_repository("memory");
478 assert!(
479 repo.is_some(),
480 "default 'memory' cache repository should be registered"
481 );
482 assert_eq!(repo.unwrap().name(), "memory");
483 }
484}