This is an automated email from the ASF dual-hosted git repository.

pnoltes pushed a commit to branch feature/599-provide-and-use-c-service-in-rust
in repository https://gitbox.apache.org/repos/asf/celix.git

commit 3ef7b52989a202b5b7b2117fcfede58a57716a70
Author: Pepijn Noltes <pepijnnol...@gmail.com>
AuthorDate: Fri Aug 25 15:13:08 2023 +0200

    Add use_service poc for Rust experiment
---
 misc/experimental/rust/celix/src/bundle_context.rs | 134 ++++++++++++++++++++-
 misc/experimental/rust/rust_shell_api/src/lib.rs   |  10 +-
 .../rust/shell_command_bundle/src/lib.rs           | 133 ++++++++++++++------
 3 files changed, 239 insertions(+), 38 deletions(-)

diff --git a/misc/experimental/rust/celix/src/bundle_context.rs 
b/misc/experimental/rust/celix/src/bundle_context.rs
index 88fc4ed6..01022fa6 100644
--- a/misc/experimental/rust/celix/src/bundle_context.rs
+++ b/misc/experimental/rust/celix/src/bundle_context.rs
@@ -21,13 +21,16 @@ use std::any::type_name;
 use std::any::Any;
 use std::collections::HashMap;
 use std::ffi::c_void;
-use std::ops::DerefMut;
 use std::ptr::null_mut;
 use std::sync::Arc;
 use std::sync::Mutex;
 use std::sync::Weak;
 
 use celix_bindings::celix_bundleContext_log;
+use celix_bindings::celix_service_use_options_t;
+use celix_bindings::celix_service_filter_options_t;
+use celix_bindings::celix_bundleContext_useServicesWithOptions;
+use celix_bindings::celix_bundleContext_useServiceWithOptions;
 use celix_bindings::celix_bundleContext_registerServiceWithOptions;
 use celix_bindings::celix_bundleContext_unregisterService;
 use celix_bindings::celix_bundle_context_t;
@@ -193,12 +196,127 @@ impl ServiceRegistrationBuilder<'_> {
         self.validate()?;
         let svc_ptr = self.get_c_svc();
         unsafe {
-            //TODO make unsafe part smaller (if possible)
             self.build_unsafe(svc_ptr)
         }
     }
 }
 
+pub struct ServiceUseBuilder<'a> {
+    ctx: &'a BundleContextImpl,
+    many: bool,
+    service_name: String,
+    filter: String,
+    callback: Option<Box<Box<dyn Fn(&dyn Any)>>>
+}
+
+impl ServiceUseBuilder<'_> {
+    fn new(ctx: &BundleContextImpl, many: bool) -> ServiceUseBuilder {
+        ServiceUseBuilder {
+            ctx,
+            many,
+            service_name: "".to_string(),
+            filter: "".to_string(),
+            callback: None,
+        }
+    }
+
+    pub fn with_service<T: ?Sized + 'static>(&mut self) -> &mut Self {
+        self.service_name = type_name::<T>().to_string();
+        self
+    }
+
+    #[doc = " @brief Provide a callback which will be called when a service is 
available. T must be a Sized (non trait) type, due to the use of downcast_ref"]
+    pub fn with_callback<T: Sized + 'static>(&mut self, closure: Box<dyn 
Fn(&T)>)-> &mut Self {
+        if self.service_name.is_empty() {
+            self.with_service::<T>();
+        }
+
+        let any_closure = Box::new(move |any_svc: &dyn Any| {
+            if let Some(svc) = any_svc.downcast_ref::<T>() {
+                closure(svc);
+            }
+        });
+
+        self.callback = Some(Box::new(any_closure));
+        self
+    }
+
+    #[doc = " @brief Provide a callback which will be called when a service is 
available, with a dyn Any argument. Note that this is useful for trait 
objects."]
+    pub fn with_any_callback(&mut self, closure: Box<dyn Fn(&dyn Any)>)-> &mut 
Self {
+        self.callback = Some(Box::new(closure)); //note double boxed
+        self
+    }
+
+    pub fn with_service_name(&mut self, name: &str) -> &mut Self {
+        self.service_name = name.to_string();
+        self
+    }
+
+    pub fn with_filter(&mut self, filter: &str) -> &mut Self {
+        self.filter = filter.to_string();
+        self
+    }
+
+    fn validate(&self) -> Result<(), Error> {
+        if self.callback.is_none() || self.service_name.is_empty() {
+            return Err(Error::BundleException);
+        }
+        Ok(())
+    }
+
+    unsafe extern "C" fn use_service_c_callback(handle: *mut c_void, svc: *mut 
c_void) {
+        let boxed_fn = Box::from_raw(handle as *mut Box<dyn Fn(&dyn Any)>);
+        let any_svc_ptr = svc as *mut dyn Any;
+        let any_svc_ref = any_svc_ptr.as_ref().unwrap();
+        boxed_fn(any_svc_ref);
+    }
+
+    pub fn build(&mut self) ->  Result<isize, Error> {
+        self.validate()?;
+
+        let c_service_name = 
std::ffi::CString::new(self.service_name.as_str()).unwrap();
+        let c_filter = std::ffi::CString::new(self.filter.as_str()).unwrap();
+        let c_service_name_ptr: *const i8 = c_service_name.as_ptr();
+
+        //Note filter is for now unused, introduce when updating to use of 
celix_bundleContext_useServiceWithOptions
+        let c_filter_ptr: *const i8 = if self.filter.is_empty() { null_mut()} 
else {c_filter.as_ptr() };
+
+        let boxed_fn = self.callback.take().unwrap();
+        let fn_ptr = Box::into_raw(boxed_fn) as *mut c_void;
+
+
+        let opts = celix_service_use_options_t {
+            filter: celix_service_filter_options_t {
+                serviceName: c_service_name_ptr,
+                versionRange: null_mut(),
+                filter: c_filter_ptr,
+                serviceLanguage: null_mut(),
+                ignoreServiceLanguage: false,
+            },
+            waitTimeoutInSeconds: 0.0,
+            callbackHandle: fn_ptr,
+            use_: Some(Self::use_service_c_callback),
+            useWithProperties: None,
+            useWithOwner: None,
+            flags: 0,
+        };
+
+        unsafe {
+            if self.many {
+                let count = 
celix_bundleContext_useServicesWithOptions(self.ctx.get_c_bundle_context(), 
&opts);
+                Ok(count as isize)
+            } else {
+                let called = 
celix_bundleContext_useServiceWithOptions(self.ctx.get_c_bundle_context(), 
&opts);
+                if called {
+                    Ok(1)
+                } else {
+                    Ok(0)
+                }
+            }
+        }
+    }
+}
+
 pub trait BundleContext {
     fn get_c_bundle_context(&self) -> *mut celix_bundle_context_t;
 
@@ -217,6 +335,10 @@ pub trait BundleContext {
     fn log_fatal(&self, message: &str);
 
     fn register_service(&self) -> ServiceRegistrationBuilder;
+
+    fn use_service(&self) -> ServiceUseBuilder;
+
+    fn use_services(&self) -> ServiceUseBuilder;
 }
 
 struct BundleContextImpl {
@@ -305,6 +427,14 @@ impl BundleContext for BundleContextImpl {
     fn register_service(&self) -> ServiceRegistrationBuilder {
         ServiceRegistrationBuilder::new(self)
     }
+
+    fn use_service(&self) -> ServiceUseBuilder {
+        ServiceUseBuilder::new(self, false)
+    }
+
+    fn use_services(&self) -> ServiceUseBuilder {
+        ServiceUseBuilder::new(self, true)
+    }
 }
 
 pub fn bundle_context_new(c_bundle_context: *mut celix_bundle_context_t) -> 
Arc<dyn BundleContext> {
diff --git a/misc/experimental/rust/rust_shell_api/src/lib.rs 
b/misc/experimental/rust/rust_shell_api/src/lib.rs
index 79a11de2..0969f86c 100644
--- a/misc/experimental/rust/rust_shell_api/src/lib.rs
+++ b/misc/experimental/rust/rust_shell_api/src/lib.rs
@@ -24,6 +24,12 @@ pub const COMMAND_NAME: &str = "command.name";
 pub const COMMAND_USAGE: &str = "command.usage";
 pub const COMMAND_DESCRIPTION: &str = "command.description";
 
-pub trait RustShellCommand {
-    fn execute_command(&mut self, command_line: &str) -> Result<(), Error>;
+#[doc = "A trait to implement a Celix Shell Command"]
+pub trait RustShellCommandTrait {
+    fn execute_command(&self, command_line: &str) -> Result<(), Error>;
+}
+
+#[doc = "A struct to register a Rust Shell Command"]
+pub struct RustShellCommandStruct {
+    pub execute_command: Box<dyn Fn(&str) -> Result<(), Error>>
 }
diff --git a/misc/experimental/rust/shell_command_bundle/src/lib.rs 
b/misc/experimental/rust/shell_command_bundle/src/lib.rs
index 0ab73f78..50d2905f 100644
--- a/misc/experimental/rust/shell_command_bundle/src/lib.rs
+++ b/misc/experimental/rust/shell_command_bundle/src/lib.rs
@@ -21,14 +21,15 @@ extern crate celix;
 extern crate celix_bindings;
 extern crate rust_shell_api;
 
+use std::any::Any;
 use std::ffi::c_char;
 use std::ffi::c_void;
-use std::sync::{Arc, Mutex};
+use std::sync::Arc;
 
 use celix::BundleActivator;
 use celix::BundleContext;
 use celix::Error;
-use rust_shell_api::RustShellCommand;
+use rust_shell_api::{RustShellCommandTrait, RustShellCommandStruct};
 
 use celix_bindings::celix_shell_command_t;
 use celix_bindings::FILE;
@@ -53,17 +54,18 @@ impl CShellCommandImpl {
             return false;
         }
         unsafe {
-            println!("call_execute_command");
             let obj = &mut *(handle as *mut CShellCommandImpl);
-            let str_command_line = 
std::ffi::CStr::from_ptr(command_line).to_str().unwrap();
-            obj.execute_command(str_command_line);
+            let str_command_line = 
std::ffi::CStr::from_ptr(command_line).to_str();
+            if str_command_line.is_err() {
+                return false;
+            }
+            obj.execute_command(str_command_line.unwrap());
         }
         true
     }
 
     fn execute_command(&mut self, command_line: &str) {
-        self.ctx
-            .log_info(format!("Execute command: {}", command_line).as_str());
+        self.ctx.log_info(format!("Execute command: \"{}\"", 
command_line).as_str());
     }
 }
 
@@ -80,8 +82,8 @@ impl RustShellCommandImpl {
     }
 }
 
-impl RustShellCommand for RustShellCommandImpl {
-    fn execute_command(&mut self, command_line: &str) -> Result<(), Error> {
+impl RustShellCommandTrait for RustShellCommandImpl {
+    fn execute_command(&self, command_line: &str) -> Result<(), Error> {
         self.ctx
             .log_info(format!("Execute command: {}.", command_line).as_str());
         Ok(())
@@ -92,26 +94,13 @@ struct ShellCommandActivator {
     ctx: Arc<dyn BundleContext>,
     //log_helper: Box<dyn LogHelper>,
     shell_command_provider: CShellCommandImpl,
-    c_registration: Option<celix::ServiceRegistration>,
-    rust_registration: Option<celix::ServiceRegistration>,
-    rust_registration2: Option<celix::ServiceRegistration>,
+    registrations: Vec<celix::ServiceRegistration>,
 }
 
-impl BundleActivator for ShellCommandActivator {
-    fn new(ctx: Arc<dyn celix::BundleContext>) -> Self {
-        let result = ShellCommandActivator {
-            ctx: ctx.clone(),
-            shell_command_provider: CShellCommandImpl::new(ctx.clone()),
-            //log_helper: log_helper_new(&*ctx, "ShellCommandBundle"),
-            c_registration: None,
-            rust_registration: None,
-            rust_registration2: None,
-        };
-        result
-    }
-    fn start(&mut self) -> Result<(), Error> {
+impl ShellCommandActivator {
 
-        //C service registered as direct type
+    fn register_services(&mut self) -> Result<(), Error> {
+        //Register C service registered as value
         let registration = self.ctx.register_service()
             .with_service(celix_shell_command_t {
                 handle: &mut self.shell_command_provider as *mut 
CShellCommandImpl as *mut c_void,
@@ -121,28 +110,104 @@ impl BundleActivator for ShellCommandActivator {
             .with_property("command.name", "exe_c_command_in_rust")
             .with_property("command.description", "Simple command written in 
Rust")
             .build()?;
-        self.c_registration = Some(registration);
+        self.registrations.push(registration);
         self.ctx.log_info("C Shell Command registered");
 
-        //Rust service register using a Box with a trait
+        //Register Rust trait service register using a Box
         let rust_shell_command = 
Box::new(RustShellCommandImpl::new(self.ctx.clone()));
-        let rust_shell_command = Box::<dyn 
RustShellCommand>::from(rust_shell_command);
+        let rust_shell_command = Box::<dyn 
RustShellCommandTrait>::from(rust_shell_command);
         let registration = self.ctx.register_service()
             //maybe make svc types more explicit, e.g.with type parameters
             .with_boxed_service(rust_shell_command)
             .with_property(rust_shell_api::COMMAND_NAME, "exe_rust_command")
-            .with_property(rust_shell_api::COMMAND_DESCRIPTION, "Simple 
command written in Rust")
+            .with_property(rust_shell_api::COMMAND_DESCRIPTION, "Simple 
command written in a Rust trait")
+            .build()?;
+        self.registrations.push(registration);
+        self.ctx.log_info("Rust trait Shell Command registered");
+
+
+        //Register Rust struct service (with closures) as value
+        let cloned_ctx = self.ctx.clone();
+        let rust_shell_command = RustShellCommandStruct {
+            execute_command: Box::new(move |command_line: &str| {
+                cloned_ctx.log_info(format!("Execute command: {}", 
command_line).as_str());
+                Ok(())
+            }),
+        };
+        let registration = self.ctx.register_service()
+            .with_service(rust_shell_command)
+            .with_property("command.name", "exe_rust_command2")
+            .with_property("command.description", "Simple command written in a 
Rust struct using a Rust closure")
             .build()?;
-        self.rust_registration = Some(registration);
+        self.registrations.push(registration);
+        self.ctx.log_info("Rust struct Shell Command registered");
+        Ok(())
+    }
 
+    fn use_services(&mut self) -> Result<(), Error> {
+        //test using C service
+
+        self.ctx.log_info("Use C service command service");
+        let count = self.ctx.use_services()
+            .with_service_name("celix_shell_command")
+            .with_filter("(command.name=exe_c_command_in_rust)")
+            .with_callback(Box::new( |svc: &celix_shell_command_t| {
+                if let Some(exe_cmd) = svc.executeCommand {
+                    let c_str = std::ffi::CString::new("test").unwrap();
+                    unsafe {
+                        exe_cmd(svc.handle, c_str.as_ptr() as *const c_char, 
std::ptr::null_mut(), std::ptr::null_mut());
+                    }
+                }
+            }))
+            .build()?;
+        self.ctx.log_info(format!("Found {} celix_shell_command_t services", 
count).as_str());
+
+        //test using Rust trait service
+        self.ctx.log_info("Use Rust trait service command service");
+        let count = self.ctx.use_services()
+            .with_service::<dyn RustShellCommandTrait>()
+            .with_any_callback(Box::new( |svc: &dyn Any| {
+                let typed_svc = svc.downcast_ref::<RustShellCommandImpl>();
+                if let Some(svc) = typed_svc {
+                    let _ = svc.execute_command("test");
+                }
+            }))
+            .build()?;
+        self.ctx.log_info(format!("Found {} RustShellCommandTrait services", 
count).as_str());
+
+        self.ctx.log_info("Use Rust struct service command service");
+        let count = self.ctx.use_services()
+            .with_callback(Box::new( |svc: &RustShellCommandStruct| {
+                let exe_cmd = svc.execute_command.as_ref();
+                let _ = exe_cmd("test");
+            }))
+            .build()?;
+        self.ctx.log_info(format!("Found {} RustShellCommandStruct services", 
count).as_str());
+
+        self.ctx.log_info("Rust Shell Command started");
+        Ok(())
+    }
+}
+
+impl BundleActivator for ShellCommandActivator {
+    fn new(ctx: Arc<dyn celix::BundleContext>) -> Self {
+        let result = ShellCommandActivator {
+            ctx: ctx.clone(),
+            shell_command_provider: CShellCommandImpl::new(ctx.clone()),
+            //log_helper: log_helper_new(&*ctx, "ShellCommandBundle"),
+            registrations: Vec::new(),
+        };
+        result
+    }
+    fn start(&mut self) -> Result<(), Error> {
+        self.register_services()?;
+        self.use_services()?;
         self.ctx.log_info("Rust Shell Command started");
         Ok(())
     }
 
     fn stop(&mut self) -> Result<(), Error> {
-        self.rust_registration2 = None;
-        self.rust_registration = None;
-        self.c_registration = None;
+        self.registrations.clear();
         self.ctx.log_info("Rust Shell Command stopped");
         Ok(())
     }

Reply via email to