use std::collections::BTreeMap;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use crate::context::CamelContext;
use crate::dsl::RouteBuilder;
use crate::error::{CamelError, Result};
use crate::model::RouteDefinition;
pub const ROUTE_TEMPLATE_ID: &str = "routeTemplateId";
#[derive(Clone, Debug, Default, PartialEq)]
pub struct TemplateParam {
pub name: String,
pub default: Option<String>,
pub required: bool,
pub description: Option<String>,
}
impl TemplateParam {
pub fn required(name: impl Into<String>) -> Self {
TemplateParam { name: name.into(), default: None, required: true, description: None }
}
pub fn optional(name: impl Into<String>, default: impl Into<String>) -> Self {
TemplateParam { name: name.into(), default: Some(default.into()), required: false, description: None }
}
pub fn optional_no_default(name: impl Into<String>) -> Self {
TemplateParam { name: name.into(), default: None, required: false, description: None }
}
pub fn with_description(mut self, d: impl Into<String>) -> Self {
self.description = Some(d.into());
self
}
}
#[derive(Clone, Debug, Default)]
pub struct TemplateParams {
values: BTreeMap<String, String>,
route_id: String,
template_id: String,
}
impl TemplateParams {
pub fn get(&self, name: &str) -> &str {
self.values.get(name).map(|s| s.as_str()).unwrap_or("")
}
pub fn get_opt(&self, name: &str) -> Option<&str> {
self.values.get(name).map(|s| s.as_str())
}
pub fn get_or<'a>(&'a self, name: &str, default: &'a str) -> &'a str {
self.get_opt(name).unwrap_or(default)
}
pub fn contains(&self, name: &str) -> bool {
self.values.contains_key(name)
}
pub fn values(&self) -> &BTreeMap<String, String> {
&self.values
}
pub fn route_id(&self) -> &str {
&self.route_id
}
pub fn template_id(&self) -> &str {
&self.template_id
}
pub fn resolve(&self, text: &str) -> String {
let mut out = text.to_string();
for (k, v) in &self.values {
out = out.replace(&format!("{{{{{}}}}}", k), v);
}
out
}
}
pub type TemplateBuilderFn = Arc<dyn Fn(&mut RouteBuilder, &TemplateParams) + Send + Sync>;
pub struct RouteTemplate {
pub id: String,
pub parameters: Vec<TemplateParam>,
pub description: Option<String>,
builder: TemplateBuilderFn,
}
impl RouteTemplate {
pub fn resolve_parameters(&self, given: &BTreeMap<String, String>) -> Result<BTreeMap<String, String>> {
let mut values = given.clone();
let mut missing = Vec::new();
for p in &self.parameters {
if values.contains_key(&p.name) {
continue;
}
match &p.default {
Some(d) => {
values.insert(p.name.clone(), d.clone());
}
None if p.required => missing.push(p.name.clone()),
None => {}
}
}
if !missing.is_empty() {
return Err(CamelError::illegal_argument(format!(
"Route template {} the following mandatory parameters must be provided: {}",
self.id,
missing.join(", ")
)));
}
Ok(values)
}
}
static TEMPLATE_ROUTE_COUNTER: AtomicUsize = AtomicUsize::new(0);
impl CamelContext {
pub fn add_route_template<F>(&self, id: &str, params: &[TemplateParam], f: F)
where
F: Fn(&mut RouteBuilder, &TemplateParams) + Send + Sync + 'static,
{
self.add_route_template_with_description(id, None, params, f)
}
pub fn add_route_template_with_description<F>(&self, id: &str, description: Option<&str>, params: &[TemplateParam], f: F)
where
F: Fn(&mut RouteBuilder, &TemplateParams) + Send + Sync + 'static,
{
let t = RouteTemplate { id: id.to_string(), parameters: params.to_vec(), description: description.map(|s| s.to_string()), builder: Arc::new(f) };
crate::management::state(self).templates.write().unwrap().insert(id.to_string(), Arc::new(t));
}
pub fn route_template_ids(&self) -> Vec<String> {
crate::management::state(self).templates.read().unwrap().keys().cloned().collect()
}
pub fn route_template(&self, id: &str) -> Option<Arc<RouteTemplate>> {
crate::management::state(self).templates.read().unwrap().get(id).cloned()
}
pub fn remove_route_template(&self, id: &str) -> bool {
crate::management::state(self).templates.write().unwrap().remove(id).is_some()
}
pub fn add_route_from_template(&self, route_id: &str, template_id: &str, params: BTreeMap<String, String>) -> Result<String> {
self.add_route_from_template_with(route_id, template_id, params, None)
}
fn add_route_from_template_with(
&self,
route_id: &str,
template_id: &str,
params: BTreeMap<String, String>,
configurer: Option<&dyn Fn(&mut RouteDefinition)>,
) -> Result<String> {
let t = self
.route_template(template_id)
.ok_or_else(|| CamelError::illegal_argument(format!("Cannot find RouteTemplate with id {}", template_id)))?;
let values = t.resolve_parameters(¶ms)?;
let route_id = if route_id.is_empty() { self.generate_template_route_id() } else { route_id.to_string() };
if self.route_ids().contains(&route_id) {
return Err(CamelError::new(crate::error::kinds::FAILED_TO_CREATE_ROUTE, format!("Route with id {} already exists", route_id)));
}
let tp = TemplateParams { values, route_id: route_id.clone(), template_id: template_id.to_string() };
let mut b = RouteBuilder::new(self.clone());
(t.builder)(&mut b, &tp);
let mut defs = b.into_definitions()?;
if defs.len() != 1 {
return Err(CamelError::illegal_argument(format!("Route template {} must define exactly one route (found {})", template_id, defs.len())));
}
let mut def = defs.pop().unwrap();
def.id = Some(route_id.clone());
def.properties.insert(ROUTE_TEMPLATE_ID.to_string(), template_id.to_string());
for (k, v) in tp.values() {
def.properties.insert(format!("templateParameter.{}", k), v.clone());
}
if let Some(c) = configurer {
c(&mut def);
}
self.add_route_definition(def)?;
Ok(route_id)
}
fn generate_template_route_id(&self) -> String {
let existing = self.route_ids();
loop {
let n = TEMPLATE_ROUTE_COUNTER.fetch_add(1, Ordering::Relaxed) + 1;
let id = format!("route{}", n);
if !existing.contains(&id) {
return id;
}
}
}
}
pub struct TemplatedRouteBuilder {
ctx: CamelContext,
template_id: String,
route_id: String,
params: BTreeMap<String, String>,
configurer: Option<Box<dyn Fn(&mut RouteDefinition)>>,
}
impl TemplatedRouteBuilder {
pub fn new(ctx: &CamelContext, template_id: &str) -> Self {
TemplatedRouteBuilder { ctx: ctx.clone(), template_id: template_id.to_string(), route_id: String::new(), params: BTreeMap::new(), configurer: None }
}
pub fn route_id(mut self, id: &str) -> Self {
self.route_id = id.to_string();
self
}
pub fn parameter(mut self, name: &str, value: impl ToString) -> Self {
self.params.insert(name.to_string(), value.to_string());
self
}
pub fn parameters(mut self, params: BTreeMap<String, String>) -> Self {
self.params.extend(params);
self
}
pub fn configure(mut self, f: impl Fn(&mut RouteDefinition) + 'static) -> Self {
self.configurer = Some(Box::new(f));
self
}
pub fn add(self) -> Result<String> {
let TemplatedRouteBuilder { ctx, template_id, route_id, params, configurer } = self;
ctx.add_route_from_template_with(&route_id, &template_id, params, configurer.as_deref())
}
}