Skip to content

Commit c74345b

Browse files
authored
feat: Add cause to native exception (apache#63)
1 parent 8246739 commit c74345b

File tree

1 file changed

+69
-21
lines changed

1 file changed

+69
-21
lines changed

core/src/jvm_bridge/mod.rs

Lines changed: 69 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,8 @@ pub struct JVMClasses<'a> {
205205
pub class_get_name_method: JMethodID,
206206
/// Cached method ID for "java.lang.Throwable#getMessage"
207207
pub throwable_get_message_method: JMethodID,
208+
/// Cached method ID for "java.lang.Throwable#getCause"
209+
pub throwable_get_cause_method: JMethodID,
208210

209211
/// The CometMetricNode class. Used for updating the metrics.
210212
pub comet_metric_node: CometMetricNode<'a>,
@@ -213,6 +215,7 @@ pub struct JVMClasses<'a> {
213215
}
214216

215217
unsafe impl<'a> Send for JVMClasses<'a> {}
218+
216219
unsafe impl<'a> Sync for JVMClasses<'a> {}
217220

218221
/// Keeps global references to JVM classes. Used for JNI calls to JVM.
@@ -241,10 +244,16 @@ impl JVMClasses<'_> {
241244
.get_method_id(clazz, "getMessage", "()Ljava/lang/String;")
242245
.unwrap();
243246

247+
let clazz = env.find_class("java/lang/Throwable").unwrap();
248+
let throwable_get_cause_method = env
249+
.get_method_id(clazz, "getCause", "()Ljava/lang/Throwable;")
250+
.unwrap();
251+
244252
JVMClasses {
245253
object_get_class_method,
246254
class_get_name_method,
247255
throwable_get_message_method,
256+
throwable_get_cause_method,
248257
comet_metric_node: CometMetricNode::new(env).unwrap(),
249258
comet_exec: CometExec::new(env).unwrap(),
250259
}
@@ -277,54 +286,93 @@ pub(crate) fn check_exception(env: &mut JNIEnv) -> CometResult<Option<CometError
277286
Ok(result)
278287
}
279288

280-
/// Given a `JThrowable` which is thrown from calling a Java method on the native side,
281-
/// this converts it into a `CometError::JavaException` with the exception class name
282-
/// and exception message. This error can then be populated to the JVM side to let
283-
/// users know the cause of the native side error.
284-
pub(crate) fn convert_exception(
289+
/// get the class name of the exception by:
290+
/// 1. get the `Class` object of the input `throwable` via `Object#getClass` method
291+
/// 2. get the exception class name via calling `Class#getName` on the above object
292+
fn get_throwable_class_name(
285293
env: &mut JNIEnv,
294+
jvm_classes: &JVMClasses,
286295
throwable: &JThrowable,
287-
) -> CometResult<CometError> {
296+
) -> CometResult<String> {
288297
unsafe {
289-
let cache = JVMClasses::get();
290-
291-
// get the class name of the exception by:
292-
// 1. get the `Class` object of the input `throwable` via `Object#getClass` method
293-
// 2. get the exception class name via calling `Class#getName` on the above object
294298
let class_obj = env
295299
.call_method_unchecked(
296300
throwable,
297-
cache.object_get_class_method,
301+
jvm_classes.object_get_class_method,
298302
ReturnType::Object,
299303
&[],
300304
)?
301305
.l()?;
302-
let exception_class_name = env
306+
let class_name = env
303307
.call_method_unchecked(
304308
class_obj,
305-
cache.class_get_name_method,
309+
jvm_classes.class_get_name_method,
306310
ReturnType::Object,
307311
&[],
308312
)?
309313
.l()?
310314
.into();
311-
let exception_class_name_str = env.get_string(&exception_class_name)?.into();
315+
let class_name_str = env.get_string(&class_name)?.into();
312316

313-
// get the exception message via calling `Throwable#getMessage` on the throwable object
317+
Ok(class_name_str)
318+
}
319+
}
320+
321+
/// Get the exception message via calling `Throwable#getMessage` on the throwable object
322+
fn get_throwable_message(
323+
env: &mut JNIEnv,
324+
jvm_classes: &JVMClasses,
325+
throwable: &JThrowable,
326+
) -> CometResult<String> {
327+
unsafe {
314328
let message = env
315329
.call_method_unchecked(
316330
throwable,
317-
cache.throwable_get_message_method,
331+
jvm_classes.throwable_get_message_method,
318332
ReturnType::Object,
319333
&[],
320334
)?
321335
.l()?
322336
.into();
323337
let message_str = env.get_string(&message)?.into();
324338

325-
Ok(CometError::JavaException {
326-
class: exception_class_name_str,
327-
msg: message_str,
328-
})
339+
let cause: JThrowable = env
340+
.call_method_unchecked(
341+
throwable,
342+
jvm_classes.throwable_get_cause_method,
343+
ReturnType::Object,
344+
&[],
345+
)?
346+
.l()?
347+
.into();
348+
349+
if !cause.is_null() {
350+
let cause_class_name = get_throwable_class_name(env, jvm_classes, &cause)?;
351+
let cause_message = get_throwable_message(env, jvm_classes, &cause)?;
352+
Ok(format!(
353+
"{}\nCaused by: {}: {}",
354+
message_str, cause_class_name, cause_message
355+
))
356+
} else {
357+
Ok(message_str)
358+
}
329359
}
330360
}
361+
362+
/// Given a `JThrowable` which is thrown from calling a Java method on the native side,
363+
/// this converts it into a `CometError::JavaException` with the exception class name
364+
/// and exception message. This error can then be populated to the JVM side to let
365+
/// users know the cause of the native side error.
366+
pub(crate) fn convert_exception(
367+
env: &mut JNIEnv,
368+
throwable: &JThrowable,
369+
) -> CometResult<CometError> {
370+
let cache = JVMClasses::get();
371+
let exception_class_name_str = get_throwable_class_name(env, cache, throwable)?;
372+
let message_str = get_throwable_message(env, cache, throwable)?;
373+
374+
Ok(CometError::JavaException {
375+
class: exception_class_name_str,
376+
msg: message_str,
377+
})
378+
}

0 commit comments

Comments
 (0)