jdbc 0.1.0

Rust bindings to the Java Jdbc.
Documentation
use std::{collections::HashMap, sync::Arc};

pub use jni::JNIVersion;
use jni::{
    objects::{GlobalRef, JObject},
    InitArgsBuilder, JavaVM,
};

use crate::{
    errors::InitError,
    wrapper::{
        hikari::{HikariConfig, HikariDataSource},
        properties::Properties,
    },
    Datasource,
};

type FactoryFn =
    Box<dyn Fn(&JavaVM, &HashMap<String, String>) -> Result<GlobalRef, InitError> + 'static>;

pub struct Builder {
    properties: HashMap<String, String>,
    pool_type: PoolType,
    factory: Option<FactoryFn>,
    vm: Option<Arc<JavaVM>>,
}

pub enum PoolType {
    HikariCP,
    Custom,
}

fn hikari(vm: &JavaVM, properties: &HashMap<String, String>) -> Result<GlobalRef, InitError> {
    let mut env = vm.attach_current_thread()?;
    let mut props = Properties::new(&mut env)?;
    for (key, value) in properties {
        props.set_property(key.as_str(), value.as_str())?;
    }
    let config = HikariConfig::new(&mut env, props)?;
    let datasource = HikariDataSource::new(&mut env, config)?;
    let datasource: JObject = datasource.into();
    let global_ref = env.new_global_ref(datasource)?;

    Ok(global_ref)
}

impl Builder {
    pub fn new() -> Self {
        Builder {
            properties: HashMap::new(),
            pool_type: PoolType::HikariCP,
            factory: None,
            vm: None,
        }
    }

    pub fn pool_type(mut self, pool_type: PoolType) -> Self {
        self.pool_type = pool_type;
        self
    }

    pub fn factory(mut self, factory: FactoryFn) -> Self {
        self.factory = Some(factory);
        self
    }

    pub fn vm(mut self, vm: Arc<JavaVM>) -> Self {
        self.vm = Some(vm);
        self
    }

    pub fn property(mut self, k: &str, v: &str) -> Self {
        self.properties.insert(k.to_owned(), v.to_owned());
        self
    }

    pub fn jdbc_url(mut self, url: &str) -> Self {
        self.properties.insert("jdbcUrl".to_owned(), url.to_owned());
        self
    }

    pub fn driver_class(mut self, url: &str) -> Self {
        self.properties
            .insert("driverClassName".to_owned(), url.to_owned());
        self
    }

    pub fn username(mut self, username: &str) -> Self {
        self.properties
            .insert("username".to_owned(), username.to_owned());
        self
    }

    pub fn password(mut self, password: &str) -> Self {
        self.properties
            .insert("password".to_owned(), password.to_owned());
        self
    }

    pub fn build(self) -> Result<Datasource, InitError> {
        let vm = {
            if let Some(vm) = self.vm {
                vm
            } else {
                Arc::new(JvmBuilder::new().build()?)
            }
        };

        let datasource = {
            match self.pool_type {
                PoolType::HikariCP => hikari(&vm, &self.properties),
                PoolType::Custom => {
                    if let Some(factory) = self.factory {
                        (*factory)(&vm, &self.properties)
                    } else {
                        Err(InitError::NoFactory)
                    }
                }
            }
        }?;
        {
            check_datasource(&vm, &datasource)?;
        }
        Ok(Datasource::new(vm, datasource))
    }
}

fn check_datasource(vm: &Arc<JavaVM>, datasource: &GlobalRef) -> Result<(), InitError> {
    let mut env = vm.attach_current_thread()?;
    let class = env.find_class("javax/sql/DataSource")?;
    let object = &*datasource;
    let is_ds = env.is_instance_of(object, class)?;
    if !is_ds {
        return Err(InitError::IsNotDatasource);
    }
    Ok(())
}

pub struct JvmBuilder {
    version: JNIVersion,
    classpath: String,
    xmx: u32,
    xms: u32,
    vm_options: Vec<String>,
}

impl JvmBuilder {
    pub fn version(mut self, version: JNIVersion) -> Self {
        self.version = version;
        self
    }

    pub fn xmx_mb(mut self, xmx: u32) -> Self {
        if self.xms > xmx {
            self.xms = xmx;
        }
        self.xmx = xmx;
        self
    }

    pub fn xms_mb(mut self, xms: u32) -> Self {
        if xms > self.xmx {
            self.xmx = xms;
        }
        self.xms = xms;
        self
    }

    pub fn classpath(mut self, classpath: &str) -> Self {
        self.classpath = classpath.to_owned();
        self
    }

    pub fn vm_option(mut self, option: &str) -> Self {
        self.vm_options.push(option.to_owned());
        self
    }

    pub fn new() -> Self {
        JvmBuilder {
            version: JNIVersion::V8,
            classpath: String::from("./libs/"),
            xmx: 72,
            xms: 72,
            vm_options: Vec::new(),
        }
    }

    pub fn build(self) -> Result<JavaVM, InitError> {
        let mut vm_builder = InitArgsBuilder::new()
            .version(self.version)
            .option(format!("-Xmx{}m", self.xmx))
            .option(format!("-Xms{}m", self.xms));
        let libs = libs(self.classpath.as_str());
        if libs.len() > 0 {
            let option = format!("-Djava.class.path={}", libs.join(";"));
            vm_builder = vm_builder.option(option);
        }
        for option in self.vm_options {
            vm_builder = vm_builder.option(option)
        }
        let jvm_args = vm_builder.build()?;

        let jvm = JavaVM::new(jvm_args)?;

        Ok(jvm)
    }
}

fn libs(dir: &str) -> Vec<String> {
    let mut libs = Vec::new();
    if let Ok(dir) = std::fs::read_dir(dir) {
        for lib in dir {
            if let Ok(lib) = lib {
                let path = lib.path();
                if path.is_dir() {
                    continue;
                }

                let path = path.as_os_str().to_str();
                if let Some(path) = path {
                    libs.push(path.to_string())
                }
            }
        }
    }
    return libs;
}