1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290


pub mod jvm_deps;

#[cfg(target_os = "linux")]
pub mod jvm_deps_linux;
#[cfg(target_os = "macos")]
pub mod jvm_deps_mac;
#[cfg(target_os = "windows")]
pub mod jvm_deps_windows;



// Deps for run_deployed_app
use app_dirs::AppInfo;
use j4rs;
use j4rs::{InvocationArg};
use std::convert::TryFrom;
use url::Url;
use std::path::{Path};
use std::fs;
use std::fs::File;

/**
 * This function abstracts over downloading a  given j4rs runtime tarball,
 * a hardcoded copy of OpenJDK 13, any number of maven dependencies
 * from a list of maven repositories, and
 * finally calling a true void method on any given class.
 */
pub fn run_deployed_app(
  app_name: &'static str,
  app_author: &'static str,
  rj_deps_tar_gz_url: &str,
  maven_repos: Vec<&str>,
  java_ops: Vec<&str>,
  maven_dependencies: Vec<&str>,
  http_jar_dependencies: Vec<&str>,
  explicit_class_directory: Option<String>, // When Some() this will load classes via a URLClassLoader from the given path.
  main_class_name: &str,
  main_class_void_method_name: &str
) -> Result<(), String> {
  
  let app_info = AppInfo {
    name: app_name,
    author: app_author
  };
  // JVM dependencies come from the OpenJDK build and are hard-coded
  jvm_deps::ensure_jvm_exists(app_info.clone()).unwrap();
  
  match jvm_deps::get_j4rs_basedir(
    // Projects will need to be responsible for hosting these dependencies
    rj_deps_tar_gz_url,
    app_info.clone()
  ) {
    Err(e) => {
      return Err(format!("Error downloading j4rs dependencies: {}", e));
    }
    Ok(j4s_base_path) => {
      println!("j4s_base_path={}", j4s_base_path);
      
      let mut maven_repo_structs: Vec<j4rs::MavenArtifactRepo> = Vec::new();
      for repo_url in maven_repos {
        maven_repo_structs.push(j4rs::MavenArtifactRepo::from(repo_url));
      }
      
      let mut java_op_structs: Vec<j4rs::JavaOpt> = Vec::new();
      for op in java_ops {
        java_op_structs.push(j4rs::JavaOpt::new(op));
      }
      
      let jvm_build_res = if let Some(explicit_class_directory) = explicit_class_directory.clone() {
        println!("Adding {} to classpath...", explicit_class_directory);
        // This overrides the class path search performed by JvmBuilder.build(),
        // so we duplicate that logic here.
        let sep_char = if cfg!(target_os = "windows") { ";" } else { ":" };
        
        let tmp = format!("-Djava.class.path={}{}{}",
          &explicit_class_directory, &sep_char,
          get_default_j4rs_classpath(&j4s_base_path)
        );
        
        java_op_structs.push(j4rs::JavaOpt::new(&tmp));
        j4rs::JvmBuilder::new()
          .with_base_path(&j4s_base_path)
          .with_maven_settings(j4rs::MavenSettings::new(maven_repo_structs))
          .java_opts(java_op_structs)
          .build()
      }
      else {
        j4rs::JvmBuilder::new()
          .with_base_path(&j4s_base_path)
          .with_maven_settings(j4rs::MavenSettings::new(maven_repo_structs))
          .java_opts(java_op_structs)
          .build()
      };
      
      match jvm_build_res {
        Ok(jvm) => {
          
          // Now we can load things into the JVM
          // Maven artifacts will be downloaded once and persisted under j4s_base_path
          
          for lib in maven_dependencies {
            let app_artifact = j4rs::MavenArtifact::from(lib);
            jvm.deploy_artifact(&app_artifact).unwrap();
          }
          
          // Now download .jar files (useful when maven deployments are broken)
          for http_jar_url in http_jar_dependencies {
            if ! (http_jar_url.ends_with(".jar") || http_jar_url.ends_with(".JAR")) {
              continue;
            }
            let u = Url::parse(&http_jar_url).unwrap();
            
            let mut asset_path = Path::new(&j4s_base_path).to_path_buf();
            asset_path.push("jassets");
            asset_path.push(
              u.path_segments().unwrap().collect::<Vec<&str>>().pop().unwrap_or("")
            );
            
            if ! asset_path.exists() {
              println!("Downloading {} to {:?}", http_jar_url, asset_path);
              match reqwest::get(http_jar_url) {
                Ok(mut response) => {
                  match File::create(asset_path) {
                    Ok(mut dest) => {
                      match std::io::copy(&mut response, &mut dest) {
                        Ok(_) => {
                          // File downloaded, fall through to next block for extraction
                        }
                        Err(e) => {
                          return Err(format!("{}", e));
                        }
                      }
                    }
                    Err(e) => {
                      return Err(format!("{}", e));
                    }
                  }
                }
                Err(e) => {
                  return Err(format!("{}", e));
                }
              }
            }
          }
          
          // If this is the first run j4rs will not be able to put
          // the contents of j4s_base_path/jassets/*.jar on the classpath.
          // To work around this we ALWAYS:
          //  - generate a URL[]{ } of all .jar files under jassets
          //  - create our own URLClassLoader
          //  - use that loader to invoke the given static void class & method
          
          println!("Creating URLClassLoader...");
          let mut asset_path = Path::new(&j4s_base_path).to_path_buf();
          asset_path.push("jassets");
          
          let mut url_objects = vec![];
          
          // Windows systems need a delay before reading a directory...
          if cfg!(target_os = "windows") {
            std::thread::sleep(std::time::Duration::from_millis(80));
          }
          
          // IF we were given a directory of class files, push that to our URL[] first
          if let Some(mut explicit_class_directory) = explicit_class_directory.clone() {
            if ! explicit_class_directory.ends_with("/") {
              explicit_class_directory.push_str("/");
            }
            let entry_string = format!("file://{}", explicit_class_directory);
            //println!("entry_string={}", entry_string);
            if let Ok(java_url_string) = InvocationArg::try_from(entry_string) {
              // java.lang.String ^^
              let java_url = jvm.create_instance(
                "java.net.URL",
                &[java_url_string]
              ).unwrap();
              url_objects.push(InvocationArg::from(java_url));
            }
          }
          
          for entry in fs::read_dir(asset_path).unwrap() {
            if let Ok(entry) = entry {
              let entry_string: String = entry.path().as_path().to_string_lossy().to_string();
              let entry_string = format!("file://{}", entry_string);
              //println!("entry_string={}", entry_string);
              if let Ok(java_url_string) = InvocationArg::try_from(entry_string) {
                // java.lang.String ^^
                let java_url = jvm.create_instance(
                  "java.net.URL",
                  &[java_url_string]
                ).unwrap();
                url_objects.push(InvocationArg::from(java_url));
              }
            }
          }
          
          let url_classloader = jvm.create_instance(
            "java.net.URLClassLoader",
            &[
              InvocationArg::Java {
                instance: jvm.create_java_array("java.net.URL", &url_objects).unwrap(),
                class_name: "java.net.URL".to_string(),
                serialized: false,
              }
            ]
          ).unwrap();
          let true_boolean_obj = jvm.create_instance(
              "java.lang.Boolean",
              &vec![
                InvocationArg::try_from("true").unwrap(),
              ],
          ).unwrap();
          let true_boolean_primitive = jvm.invoke(
            &true_boolean_obj,
            "booleanValue",
            &vec![]
          ).unwrap();
          let classloader = jvm.cast(&url_classloader, "java.lang.ClassLoader").unwrap();
          
          let class_obj = jvm.invoke_static(
            "java.lang.Class",
            "forName",
            &vec![
              InvocationArg::try_from(main_class_name).unwrap(),
              //InvocationArg::try_from(true).unwrap().into_primitive().unwrap(),
              InvocationArg::from(true_boolean_primitive),
              InvocationArg::from(classloader),
            ]
          ).unwrap();
          let method_obj = jvm.invoke(
            &class_obj,
            "getDeclaredMethod",
            &vec![
              InvocationArg::try_from(main_class_void_method_name).unwrap(),
              InvocationArg::from(jvm.create_java_array("java.lang.Class", &vec![]).unwrap()),
            ]
          ).unwrap();
          let main_class_instance = jvm.invoke(
            &class_obj,
            "newInstance",
            &vec![]
          ).unwrap();
          
          println!("Rust is invoking {}.{}() using reflection ...", main_class_name, main_class_void_method_name);
          let _method_invoke_res = jvm.invoke(
            &method_obj,
            "invoke",
            &vec![
              InvocationArg::from(main_class_instance),
              InvocationArg::from(jvm.create_java_array("java.lang.Object", &vec![]).unwrap()),
            ]
          );
          
          println!("Rust is exiting...");
          
        }
        Err(e) => {
          return Err(format!("e={:?}", e));
        }
      }
    }
  }
  
  Ok(())
}

fn get_default_j4rs_classpath(j4s_base_path: &str) -> String {
  use std::path::{PathBuf};
  use fs_extra::dir::get_dir_content;
  
  let mut jassets_path = PathBuf::from(j4s_base_path);
  jassets_path.push("jassets");
  
  let all_jars = get_dir_content(&jassets_path).expect("Could not read j4rs dir").files;
  // This is the j4rs jar that should be included in the classpath
  //let j4rs_jar_to_use = format!("j4rs-{}-jar-with-dependencies.jar", j4rs_version());
  let j4rs_jar_to_use = format!("j4rs-0.10.0-jar-with-dependencies.jar"); // TODO hardcoded but we need to detect off tar.gz contents?
  // Filter out possible incorrect jars of j4rs
  let filtered_jars: Vec<String> = all_jars.into_iter()
      .filter(|jar| {
          !jar.contains("j4rs-") || jar.ends_with(&j4rs_jar_to_use)
      })
      .collect();
  let sep_char = if cfg!(target_os = "windows") { ";" } else { ":" };
  let cp_string = filtered_jars.join(sep_char);
  return cp_string;  
}