use std::collections::{BTreeMap, HashMap};
use std::sync::{Arc, Mutex};
use crate::builder::ExpressionDefinition;
use crate::error::{kinds, CamelError, Result};
use crate::impl_definition_boilerplate;
use crate::message::{props, Exchange};
use crate::model::{OutputList, ProcessorDefinition, RouteContext};
use crate::processor::pipeline::{process_safely, Pipeline};
use crate::spi::{Expression, Processor};
pub trait IdempotentRepository: Send + Sync {
fn add(&self, key: &str) -> bool;
fn contains(&self, key: &str) -> bool;
fn remove(&self, key: &str) -> bool;
fn confirm(&self, _key: &str) -> bool {
true
}
fn clear(&self);
}
pub struct MemoryIdempotentRepository {
capacity: usize,
inner: Mutex<LruState>,
}
#[derive(Default)]
struct LruState {
tick: u64,
keys: HashMap<String, u64>,
order: BTreeMap<u64, String>,
}
impl MemoryIdempotentRepository {
pub fn new(capacity: usize) -> Self {
MemoryIdempotentRepository { capacity: capacity.max(1), inner: Mutex::new(LruState::default()) }
}
pub fn capacity(&self) -> usize {
self.capacity
}
pub fn size(&self) -> usize {
self.inner.lock().unwrap().keys.len()
}
}
impl Default for MemoryIdempotentRepository {
fn default() -> Self {
Self::new(1000)
}
}
pub fn memory_idempotent_repository(capacity: usize) -> Arc<MemoryIdempotentRepository> {
Arc::new(MemoryIdempotentRepository::new(capacity))
}
impl IdempotentRepository for MemoryIdempotentRepository {
fn add(&self, key: &str) -> bool {
let mut s = self.inner.lock().unwrap();
s.tick += 1;
let t = s.tick;
if let Some(old) = s.keys.insert(key.to_string(), t) {
s.order.remove(&old);
s.order.insert(t, key.to_string());
return false;
}
s.order.insert(t, key.to_string());
while s.keys.len() > self.capacity {
let (&oldest, _) = s.order.iter().next().unwrap();
let k = s.order.remove(&oldest).unwrap();
s.keys.remove(&k);
}
true
}
fn contains(&self, key: &str) -> bool {
self.inner.lock().unwrap().keys.contains_key(key)
}
fn remove(&self, key: &str) -> bool {
let mut s = self.inner.lock().unwrap();
match s.keys.remove(key) {
Some(t) => {
s.order.remove(&t);
true
}
None => false,
}
}
fn clear(&self) {
let mut s = self.inner.lock().unwrap();
s.keys.clear();
s.order.clear();
}
}
pub struct IdempotentConsumerDefinition {
pub id: Option<String>,
pub expression: ExpressionDefinition,
pub repository: Arc<dyn IdempotentRepository>,
pub eager: bool,
pub skip_duplicate: bool,
pub remove_on_failure: bool,
pub completion_eager: bool,
pub outputs: OutputList,
}
impl IdempotentConsumerDefinition {
pub fn new(expression: ExpressionDefinition, repository: Arc<dyn IdempotentRepository>) -> Self {
IdempotentConsumerDefinition {
id: None,
expression,
repository,
eager: true,
skip_duplicate: true,
remove_on_failure: true,
completion_eager: false,
outputs: OutputList::default(),
}
}
}
impl ProcessorDefinition for IdempotentConsumerDefinition {
fn name(&self) -> &str {
"idempotentConsumer"
}
fn is_block(&self) -> bool {
true
}
fn add_output(&mut self, def: Box<dyn ProcessorDefinition>) -> Result<()> {
self.outputs.push(def);
Ok(())
}
fn outputs(&self) -> Vec<&dyn ProcessorDefinition> {
self.outputs.refs()
}
fn create_processor(&self, rc: &RouteContext) -> Result<Arc<dyn Processor>> {
let child: Arc<dyn Processor> =
if self.outputs.outputs.is_empty() { Arc::new(Pipeline::new(vec![])) } else { rc.create_child_processor(&self.outputs.outputs)? };
Ok(Arc::new(IdempotentConsumer {
expression: self.expression.resolve_expression(&rc.context)?,
repository: self.repository.clone(),
eager: self.eager,
skip_duplicate: self.skip_duplicate,
remove_on_failure: self.remove_on_failure,
completion_eager: self.completion_eager,
child,
}))
}
impl_definition_boilerplate!();
}
struct IdempotentConsumer {
expression: Arc<dyn Expression>,
repository: Arc<dyn IdempotentRepository>,
eager: bool,
skip_duplicate: bool,
remove_on_failure: bool,
completion_eager: bool,
child: Arc<dyn Processor>,
}
fn complete(repo: &dyn IdempotentRepository, key: &str, failed: bool, eager: bool, remove_on_failure: bool) {
if failed {
if remove_on_failure {
repo.remove(key);
}
} else {
if !eager {
repo.add(key);
}
repo.confirm(key);
}
}
impl Processor for IdempotentConsumer {
fn process(&self, ex: &mut Exchange) -> Result<()> {
let v = self.expression.evaluate(ex)?;
if v.is_null() {
return Err(CamelError::new(kinds::NO_MESSAGE_ID, format!("No message ID could be found using expression on message exchange: {}", ex.exchange_id)));
}
let key = v.to_string();
let is_new = if self.eager { self.repository.add(&key) } else { !self.repository.contains(&key) };
if !is_new {
ex.set_property(props::DUPLICATE_MESSAGE, true);
if self.skip_duplicate {
crate::camel_debug!("IdempotentConsumer", "Ignoring duplicate message with id: {} for exchange: {}", key, ex.exchange_id);
return Ok(());
}
process_safely(self.child.as_ref(), ex);
return Ok(());
}
process_safely(self.child.as_ref(), ex);
if self.completion_eager {
complete(self.repository.as_ref(), &key, ex.is_failed(), self.eager, self.remove_on_failure);
} else {
let repo = self.repository.clone();
let (eager, rof) = (self.eager, self.remove_on_failure);
ex.add_on_completion(move |done: &Exchange| complete(repo.as_ref(), &key, done.is_failed(), eager, rof));
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lru_eviction() {
let r = MemoryIdempotentRepository::new(2);
assert!(r.add("a"));
assert!(r.add("b"));
assert!(!r.add("a")); assert!(r.add("c")); assert!(r.contains("a"));
assert!(!r.contains("b"));
assert!(r.contains("c"));
assert_eq!(r.size(), 2);
assert!(r.remove("a"));
assert!(!r.remove("a"));
r.clear();
assert_eq!(r.size(), 0);
}
}