summaryrefslogtreecommitdiff
path: root/subversion/bindings/javahl/native/JNIUtil.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/bindings/javahl/native/JNIUtil.cpp')
-rw-r--r--subversion/bindings/javahl/native/JNIUtil.cpp799
1 files changed, 476 insertions, 323 deletions
diff --git a/subversion/bindings/javahl/native/JNIUtil.cpp b/subversion/bindings/javahl/native/JNIUtil.cpp
index e52e975..2adcf2f 100644
--- a/subversion/bindings/javahl/native/JNIUtil.cpp
+++ b/subversion/bindings/javahl/native/JNIUtil.cpp
@@ -24,6 +24,13 @@
* @brief Implementation of the class JNIUtil
*/
+/* Include apr.h first, or INT64_C won't be defined properly on some C99
+ compilers, when other headers include <stdint.h> before defining some
+ macros.
+
+ See apr.h for the ugly details */
+#include <apr.h>
+
#include "JNIUtil.h"
#include "Array.h"
@@ -35,8 +42,11 @@
#include <apr_tables.h>
#include <apr_general.h>
#include <apr_lib.h>
+#include <apr_file_info.h>
+#include <apr_time.h>
#include "svn_pools.h"
+#include "svn_error.h"
#include "svn_fs.h"
#include "svn_ra.h"
#include "svn_utf.h"
@@ -44,37 +54,43 @@
#include "svn_dso.h"
#include "svn_path.h"
#include "svn_cache_config.h"
-#include <apr_file_info.h>
+#include "private/svn_atomic.h"
+#include "private/svn_utf_private.h"
#include "svn_private_config.h"
-#ifdef WIN32
-/* FIXME: We're using an internal APR header here, which means we
- have to build Subversion with APR sources. This being Win32-only,
- that should be fine for now, but a better solution must be found in
- combination with issue #850. */
-extern "C" {
-#include <arch/win32/apr_arch_utf8.h>
-};
-#endif
#include "SVNBase.h"
#include "JNIMutex.h"
#include "JNICriticalSection.h"
-#include "JNIThreadData.h"
#include "JNIStringHolder.h"
#include "Pool.h"
+
+#include "jniwrapper/jni_env.hpp"
+
// Static members of JNIUtil are allocated here.
apr_pool_t *JNIUtil::g_pool = NULL;
std::list<SVNBase*> JNIUtil::g_finalizedObjects;
JNIMutex *JNIUtil::g_finalizedObjectsMutex = NULL;
JNIMutex *JNIUtil::g_logMutex = NULL;
+JNIMutex *JNIUtil::g_configMutex = NULL;
bool JNIUtil::g_initException;
-bool JNIUtil::g_inInit;
-JNIEnv *JNIUtil::g_initEnv;
-char JNIUtil::g_initFormatBuffer[formatBufferSize];
int JNIUtil::g_logLevel = JNIUtil::noLog;
std::ofstream JNIUtil::g_logStream;
+/* The error code we will use to signal a Java exception */
+static const apr_status_t
+SVN_ERR_JAVAHL_WRAPPED = SVN_ERR_MALFUNC_CATEGORY_START
+ + SVN_ERR_CATEGORY_SIZE - 10;
+
+/**
+ * Return the JNI environment to use
+ * @return the JNI environment
+ */
+JNIEnv *JNIUtil::getEnv()
+{
+ return Java::Env().get();
+}
+
/**
* Initialize the environment for all requests.
* @param env the JNI environment for this request
@@ -84,9 +100,6 @@ bool JNIUtil::JNIInit(JNIEnv *env)
// Clear all standing exceptions.
env->ExceptionClear();
- // Remember the env parameter for the remainder of the request.
- setEnv(env);
-
// Lock the list of finalized objects.
JNICriticalSection cs(*g_finalizedObjectsMutex) ;
if (isExceptionThrown())
@@ -104,49 +117,48 @@ bool JNIUtil::JNIInit(JNIEnv *env)
return true;
}
+/* Forwarder for calling JNIGlobalInit from JNI_OnLoad(). */
+bool initialize_jni_util(JNIEnv *env)
+{
+ return JNIUtil::JNIGlobalInit(env);
+}
+
+namespace {
+
+volatile svn_atomic_t *gentle_crash_write_loc = NULL;
+
+svn_error_t *
+gently_crash_the_jvm(svn_boolean_t can_return,
+ const char *file, int line, const char *expr)
+{
+ if (!can_return)
+ {
+ // Try not to abort; aborting prevents the JVM from creating
+ // a crash log, which is oh so useful for debugging.
+ // We can't just raise a SEGV signal, either, because it will
+ // be not be caught in the context that we're interested in
+ // getting the stack trace from.
+
+ // Try reading from and writing to the zero page
+ const svn_atomic_t zeropage = svn_atomic_read(gentle_crash_write_loc);
+ svn_atomic_set(gentle_crash_write_loc, zeropage);
+ }
+
+ // Forward to the standard malfunction handler, which does call
+ // abort when !can_return; this will only happen if the write to the
+ // zero page did not cause a SEGV.
+ return svn_error_raise_on_malfunction(can_return, file, line, expr);
+}
+} // Anonymous namespace
+
/**
* Initialize the environment for all requests.
+ * This method must be called in a single-threaded context.
* @param env the JNI environment for this request
*/
bool JNIUtil::JNIGlobalInit(JNIEnv *env)
{
- // This method has to be run only once during the run a program.
- static bool run = false;
svn_error_t *err;
- if (run) // already run
- return true;
-
- run = true;
-
- // Do not run this part more than one time. This leaves a small
- // time window when two threads create their first SVNClient and
- // SVNAdmin at the same time, but I do not see a better option
- // without APR already initialized
- if (g_inInit)
- return false;
-
- g_inInit = true;
- g_initEnv = env;
-
- apr_status_t status;
-
-
-
- /* Initialize the APR subsystem, and register an atexit() function
- * to Uninitialize that subsystem at program exit. */
- status = apr_initialize();
- if (status)
- {
- if (stderr)
- {
- char buf[1024];
- apr_strerror(status, buf, sizeof(buf) - 1);
- fprintf(stderr,
- "%s: error: cannot initialize APR: %s\n",
- "svnjavahl", buf);
- }
- return FALSE;
- }
/* This has to happen before any pools are created. */
if ((err = svn_dso_initialize2()))
@@ -158,16 +170,8 @@ bool JNIUtil::JNIGlobalInit(JNIEnv *env)
return FALSE;
}
- if (0 > atexit(apr_terminate))
- {
- if (stderr)
- fprintf(stderr,
- "%s: error: atexit registration failed\n",
- "svnjavahl");
- return FALSE;
- }
-
- /* Create our top-level pool. */
+ /* Create our top-level pool.
+ N.B.: APR was initialized by JNI_OnLoad. */
g_pool = svn_pool_create(NULL);
apr_allocator_t* allocator = apr_pool_allocator_get(g_pool);
@@ -180,15 +184,25 @@ bool JNIUtil::JNIGlobalInit(JNIEnv *env)
}
svn_utf_initialize2(FALSE, g_pool); /* Optimize character conversions */
- svn_fs_initialize(g_pool); /* Avoid some theoretical issues */
- svn_ra_initialize(g_pool);
- /* We shouldn't fill the JVMs memory with FS cache data unless explictly
- requested. */
+ // Initialize the libraries we use
+ err = svn_fs_initialize(g_pool);
+ if (!err)
+ err = svn_ra_initialize(g_pool);
+ if (err)
+ {
+ if (stderr && err->message)
+ fprintf(stderr, "%s", err->message);
+
+ svn_error_clear(err);
+ return FALSE;
+ }
+
+ /* We shouldn't fill the JVMs memory with FS cache data unless
+ explicitly requested. And we don't either, because the caches get
+ allocated outside the JVM heap. Duh. */
{
svn_cache_config_t settings = *svn_cache_config_get();
- settings.cache_size = 0;
- settings.file_handle_count = 0;
settings.single_threaded = FALSE;
svn_cache_config_set(&settings);
}
@@ -197,32 +211,24 @@ bool JNIUtil::JNIGlobalInit(JNIEnv *env)
#ifdef WIN32
{
WCHAR ucs2_path[MAX_PATH];
- char *utf8_path;
+ const char *utf8_path;
const char *internal_path;
- apr_pool_t *pool;
- apr_status_t apr_err;
- apr_size_t inwords, outbytes;
- unsigned int outlength;
+ svn_error_t *err;
+ apr_pool_t *pool = svn_pool_create(g_pool);
- pool = svn_pool_create(g_pool);
/* get dll name - our locale info will be in '../share/locale' */
- inwords = sizeof(ucs2_path) / sizeof(ucs2_path[0]);
HINSTANCE moduleHandle = GetModuleHandle("libsvnjavahl-1");
- GetModuleFileNameW(moduleHandle, ucs2_path, inwords);
- inwords = lstrlenW(ucs2_path);
- outbytes = outlength = 3 * (inwords + 1);
- utf8_path = reinterpret_cast<char *>(apr_palloc(pool, outlength));
- apr_err = apr_conv_ucs2_to_utf8((const apr_wchar_t *) ucs2_path,
- &inwords, utf8_path, &outbytes);
- if (!apr_err && (inwords > 0 || outbytes == 0))
- apr_err = APR_INCOMPLETE;
- if (apr_err)
+ GetModuleFileNameW(moduleHandle, ucs2_path,
+ sizeof(ucs2_path) / sizeof(ucs2_path[0]));
+ err = svn_utf__win32_utf16_to_utf8(&utf8_path, ucs2_path, NULL, pool);
+ if (err)
{
if (stderr)
- fprintf(stderr, "Can't convert module path to UTF-8");
- return FALSE;
+ svn_handle_error2(err, stderr, false, "svn: ");
+ svn_error_clear(err);
+ return false;
}
- utf8_path[outlength - outbytes] = '\0';
+
internal_path = svn_dirent_internal_style(utf8_path, pool);
/* get base path name */
internal_path = svn_dirent_dirname(internal_path, pool);
@@ -268,16 +274,14 @@ bool JNIUtil::JNIGlobalInit(JNIEnv *env)
if (isExceptionThrown())
return false;
- // initialized the thread local storage
- if (!JNIThreadData::initThreadData())
- return false;
-
- setEnv(env);
+ g_configMutex = new JNIMutex(g_pool);
if (isExceptionThrown())
return false;
- g_initEnv = NULL;
- g_inInit = false;
+ // Set a malfunction handler that tries not to call abort, because
+ // that would prevent the JVM from creating a crash and stack log file.
+ svn_error_set_malfunction_handler(gently_crash_the_jvm);
+
return true;
}
@@ -304,18 +308,6 @@ void JNIUtil::raiseThrowable(const char *name, const char *message)
return;
env->ThrowNew(clazz, message);
- setExceptionThrown();
- env->DeleteLocalRef(clazz);
-}
-
-jstring JNIUtil::makeSVNErrorMessage(svn_error_t *err)
-{
- if (err == NULL)
- return NULL;
- std::string buffer;
- assembleErrorMessage(err, 0, APR_SUCCESS, buffer);
- jstring jmessage = makeJString(buffer.c_str());
- return jmessage;
}
void
@@ -418,10 +410,174 @@ JNIUtil::putErrorsInTrace(svn_error_t *err,
env->DeleteLocalRef(jfileName);
}
-void JNIUtil::wrappedHandleSVNError(svn_error_t *err)
+namespace {
+struct MessageStackItem
{
- std::string msg;
- assembleErrorMessage(svn_error_purge_tracing(err), 0, APR_SUCCESS, msg);
+ apr_status_t m_code;
+ std::string m_message;
+ bool m_generic;
+
+ MessageStackItem(apr_status_t code, const char* message,
+ bool generic = false)
+ : m_code(code),
+ m_message(message),
+ m_generic(generic)
+ {}
+};
+typedef std::vector<MessageStackItem> ErrorMessageStack;
+
+/*
+ * Build the error message from the svn error into buffer. This
+ * method iterates through all the chained errors
+ *
+ * @param err the subversion error
+ * @param buffer the buffer where the formated error message will
+ * be stored
+ * @return An array of error codes and messages
+ */
+ErrorMessageStack assemble_error_message(
+ svn_error_t *err, std::string &result)
+{
+ // buffer for a single error message
+ char errbuf[1024];
+ apr_status_t parent_apr_err = 0;
+ ErrorMessageStack message_stack;
+
+ /* Pretty-print the error */
+ /* Note: we can also log errors here someday. */
+
+ for (int depth = 0; err;
+ ++depth, parent_apr_err = err->apr_err, err = err->child)
+ {
+ /* When we're recursing, don't repeat the top-level message if its
+ * the same as before. */
+ if ((depth == 0 || err->apr_err != parent_apr_err)
+ && err->apr_err != SVN_ERR_JAVAHL_WRAPPED)
+ {
+ const char *message;
+ /* Is this a Subversion-specific error code? */
+ if ((err->apr_err > APR_OS_START_USEERR)
+ && (err->apr_err <= APR_OS_START_CANONERR))
+ message = svn_strerror(err->apr_err, errbuf, sizeof(errbuf));
+ /* Otherwise, this must be an APR error code. */
+ else
+ {
+ /* Messages coming from apr_strerror are in the native
+ encoding, it's a good idea to convert them to UTF-8. */
+ apr_strerror(err->apr_err, errbuf, sizeof(errbuf));
+ svn_error_t* utf8_err =
+ svn_utf_cstring_to_utf8(&message, errbuf, err->pool);
+ if (utf8_err)
+ {
+ /* Use fuzzy transliteration instead. */
+ svn_error_clear(utf8_err);
+ message = svn_utf_cstring_from_utf8_fuzzy(errbuf, err->pool);
+ }
+ }
+
+ message_stack.push_back(
+ MessageStackItem(err->apr_err, message, true));
+ }
+ if (err->message)
+ {
+ message_stack.push_back(
+ MessageStackItem(err->apr_err, err->message));
+ }
+ }
+
+ for (ErrorMessageStack::const_iterator it = message_stack.begin();
+ it != message_stack.end(); ++it)
+ {
+ if (!it->m_generic)
+ result += "svn: ";
+ result += it->m_message;
+ result += '\n';
+ }
+ return message_stack;
+}
+
+jobject construct_Jmessage_stack(const ErrorMessageStack& message_stack)
+{
+ JNIEnv *env = JNIUtil::getEnv();
+ env->PushLocalFrame(LOCAL_FRAME_SIZE);
+ if (JNIUtil::isJavaExceptionThrown())
+ return NULL;
+
+ jclass list_clazz = env->FindClass("java/util/ArrayList");
+ if (JNIUtil::isJavaExceptionThrown())
+ POP_AND_RETURN_NULL;
+ jmethodID mid = env->GetMethodID(list_clazz, "<init>", "(I)V");
+ if (JNIUtil::isJavaExceptionThrown())
+ POP_AND_RETURN_NULL;
+ jmethodID add_mid = env->GetMethodID(list_clazz, "add",
+ "(Ljava/lang/Object;)Z");
+ if (JNIUtil::isJavaExceptionThrown())
+ POP_AND_RETURN_NULL;
+ jobject jlist = env->NewObject(list_clazz, mid, jint(message_stack.size()));
+ if (JNIUtil::isJavaExceptionThrown())
+ POP_AND_RETURN_NULL;
+
+ jclass clazz = env->FindClass(JAVAHL_CLASS("/ClientException$ErrorMessage"));
+ if (JNIUtil::isJavaExceptionThrown())
+ POP_AND_RETURN_NULL;
+ mid = env->GetMethodID(clazz, "<init>",
+ "(ILjava/lang/String;Z)V");
+ if (JNIUtil::isJavaExceptionThrown())
+ POP_AND_RETURN_NULL;
+
+ for (ErrorMessageStack::const_iterator it = message_stack.begin();
+ it != message_stack.end(); ++it)
+ {
+ jobject jmessage = JNIUtil::makeJString(it->m_message.c_str());
+ if (JNIUtil::isJavaExceptionThrown())
+ POP_AND_RETURN_NULL;
+ jobject jitem = env->NewObject(clazz, mid,
+ jint(it->m_code), jmessage,
+ jboolean(it->m_generic));
+ if (JNIUtil::isJavaExceptionThrown())
+ POP_AND_RETURN_NULL;
+ env->CallBooleanMethod(jlist, add_mid, jitem);
+ if (JNIUtil::isJavaExceptionThrown())
+ POP_AND_RETURN_NULL;
+
+ env->DeleteLocalRef(jmessage);
+ env->DeleteLocalRef(jitem);
+ }
+ return env->PopLocalFrame(jlist);
+}
+} // anonymous namespace
+
+std::string JNIUtil::makeSVNErrorMessage(svn_error_t *err,
+ jstring *jerror_message,
+ jobject *jmessage_stack)
+{
+ if (jerror_message)
+ *jerror_message = NULL;
+ if (jmessage_stack)
+ *jmessage_stack = NULL;
+
+ std::string buffer;
+ err = svn_error_purge_tracing(err);
+ if (err == NULL || err->apr_err == 0
+ || !(jerror_message || jmessage_stack))
+ return buffer;
+
+ ErrorMessageStack message_stack = assemble_error_message(err, buffer);
+ if (jerror_message)
+ *jerror_message = makeJString(buffer.c_str());
+ if (jmessage_stack)
+ *jmessage_stack = construct_Jmessage_stack(message_stack);
+ return buffer;
+}
+
+jthrowable JNIUtil::wrappedCreateClientException(svn_error_t *err, jthrowable jcause)
+{
+ jstring jmessage;
+ jobject jstack;
+ std::string msg = makeSVNErrorMessage(err, &jmessage, &jstack);
+ if (JNIUtil::isJavaExceptionThrown())
+ return NULL;
+
const char *source = NULL;
#ifdef SVN_DEBUG
#ifndef SVN_ERR__TRACING
@@ -436,6 +592,9 @@ void JNIUtil::wrappedHandleSVNError(svn_error_t *err)
#endif
#endif
+ if (!jcause)
+ jcause = JNIUtil::unwrapJavaException(err);
+
// Much of the following is stolen from throwNativeException(). As much as
// we'd like to call that function, we need to do some manual stack
// unrolling, so it isn't feasible.
@@ -445,11 +604,11 @@ void JNIUtil::wrappedHandleSVNError(svn_error_t *err)
// Create a local frame for our references
env->PushLocalFrame(LOCAL_FRAME_SIZE);
if (JNIUtil::isJavaExceptionThrown())
- return;
+ return NULL;
- jclass clazz = env->FindClass(JAVA_PACKAGE "/ClientException");
+ jclass clazz = env->FindClass(JAVAHL_CLASS("/ClientException"));
if (isJavaExceptionThrown())
- POP_AND_RETURN_NOTHING();
+ POP_AND_RETURN_NULL;
if (getLogLevel() >= exceptionLog)
{
@@ -463,23 +622,24 @@ void JNIUtil::wrappedHandleSVNError(svn_error_t *err)
g_logStream << std::endl;
}
if (isJavaExceptionThrown())
- POP_AND_RETURN_NOTHING();
+ POP_AND_RETURN_NULL;
- jstring jmessage = makeJString(msg.c_str());
- if (isJavaExceptionThrown())
- POP_AND_RETURN_NOTHING();
jstring jsource = makeJString(source);
if (isJavaExceptionThrown())
- POP_AND_RETURN_NOTHING();
+ POP_AND_RETURN_NULL;
jmethodID mid = env->GetMethodID(clazz, "<init>",
- "(Ljava/lang/String;Ljava/lang/String;I)V");
+ "(Ljava/lang/String;"
+ "Ljava/lang/Throwable;"
+ "Ljava/lang/String;I"
+ "Ljava/util/List;)V");
if (isJavaExceptionThrown())
- POP_AND_RETURN_NOTHING();
- jobject nativeException = env->NewObject(clazz, mid, jmessage, jsource,
- static_cast<jint>(err->apr_err));
+ POP_AND_RETURN_NULL;
+ jobject nativeException = env->NewObject(clazz, mid, jmessage, jcause,
+ jsource, jint(err->apr_err),
+ jstack);
if (isJavaExceptionThrown())
- POP_AND_RETURN_NOTHING();
+ POP_AND_RETURN_NULL;
#ifdef SVN_ERR__TRACING
// Add all the C error stack trace information to the Java Exception
@@ -491,7 +651,7 @@ void JNIUtil::wrappedHandleSVNError(svn_error_t *err)
mid_gst = env->GetMethodID(clazz, "getStackTrace",
"()[Ljava/lang/StackTraceElement;");
if (isJavaExceptionThrown())
- POP_AND_RETURN_NOTHING();
+ POP_AND_RETURN_NULL;
}
Array stackTraceArray((jobjectArray) env->CallObjectMethod(nativeException,
mid_gst));
@@ -510,18 +670,18 @@ void JNIUtil::wrappedHandleSVNError(svn_error_t *err)
jclass stClazz = env->FindClass("java/lang/StackTraceElement");
if (isJavaExceptionThrown())
- POP_AND_RETURN_NOTHING();
+ POP_AND_RETURN_NULL;
const jsize stSize = static_cast<jsize>(newStackTrace.size());
- if (stSize != newStackTrace.size())
+ if (stSize < 0 || stSize != newStackTrace.size())
{
env->ThrowNew(env->FindClass("java.lang.ArithmeticException"),
"Overflow converting C size_t to JNI jsize");
- POP_AND_RETURN_NOTHING();
+ POP_AND_RETURN_NULL;
}
jobjectArray jStackTrace = env->NewObjectArray(stSize, stClazz, NULL);
if (isJavaExceptionThrown())
- POP_AND_RETURN_NOTHING();
+ POP_AND_RETURN_NULL;
int i = 0;
for (std::vector<jobject>::const_iterator it = newStackTrace.begin();
@@ -538,25 +698,34 @@ void JNIUtil::wrappedHandleSVNError(svn_error_t *err)
mid_sst = env->GetMethodID(clazz, "setStackTrace",
"([Ljava/lang/StackTraceElement;)V");
if (isJavaExceptionThrown())
- POP_AND_RETURN_NOTHING();
+ POP_AND_RETURN_NULL;
}
env->CallVoidMethod(nativeException, mid_sst, jStackTrace);
if (isJavaExceptionThrown())
- POP_AND_RETURN_NOTHING();
+ POP_AND_RETURN_NULL;
#endif
- env->Throw(static_cast<jthrowable>(env->PopLocalFrame(nativeException)));
+ return static_cast<jthrowable>(env->PopLocalFrame(nativeException));
}
-void JNIUtil::handleSVNError(svn_error_t *err)
+jthrowable JNIUtil::createClientException(svn_error_t *err, jthrowable jcause)
{
+ jthrowable jexc = NULL;
try {
- wrappedHandleSVNError(err);
+ jexc = wrappedCreateClientException(err, jcause);
} catch (...) {
svn_error_clear(err);
throw;
}
svn_error_clear(err);
+ return jexc;
+}
+
+void JNIUtil::handleSVNError(svn_error_t *err, jthrowable jcause)
+{
+ jthrowable jexc = createClientException(err, jcause);
+ if (jexc)
+ getEnv()->Throw(jexc);
}
void JNIUtil::putFinalizedClient(SVNBase *object)
@@ -578,120 +747,82 @@ void JNIUtil::enqueueForDeletion(SVNBase *object)
*/
void JNIUtil::handleAPRError(int error, const char *op)
{
- char *buffer = getFormatBuffer();
- if (buffer == NULL)
- return;
+ char buffer[2048];
- apr_snprintf(buffer, formatBufferSize,
+ apr_snprintf(buffer, sizeof(buffer),
_("an error occurred in function %s with return value %d"),
op, error);
throwError(buffer);
}
-/**
- * Return if an exception has been detected.
- * @return a exception has been detected
- */
-bool JNIUtil::isExceptionThrown()
+namespace {
+const char* known_exception_to_cstring(apr_pool_t* pool)
{
- // During init -> look in the global member.
- if (g_inInit)
- return g_initException;
-
- // Look in the thread local storage.
- JNIThreadData *data = JNIThreadData::getThreadData();
- return data == NULL || data->m_exceptionThrown;
-}
+ JNIEnv *env = JNIUtil::getEnv();
+ jthrowable t = env->ExceptionOccurred();
+ jclass cls = env->GetObjectClass(t);
-/**
- * Store the JNI environment for this request in the thread local
- * storage.
- * @param env the JNI environment
- */
-void JNIUtil::setEnv(JNIEnv *env)
-{
- JNIThreadData::pushNewThreadData();
- JNIThreadData *data = JNIThreadData::getThreadData();
- data->m_env = env;
- data->m_exceptionThrown = false;
-}
+ jstring jclass_name;
+ {
+ jmethodID mid = env->GetMethodID(cls, "getClass", "()Ljava/lang/Class;");
+ jobject clsobj = env->CallObjectMethod(t, mid);
+ jclass basecls = env->GetObjectClass(clsobj);
+ mid = env->GetMethodID(basecls, "getName", "()Ljava/lang/String;");
+ jclass_name = (jstring) env->CallObjectMethod(clsobj, mid);
+ }
-/**
- * Return the JNI environment to use
- * @return the JNI environment
- */
-JNIEnv *JNIUtil::getEnv()
-{
- // During init -> look into the global variable.
- if (g_inInit)
- return g_initEnv;
+ jstring jmessage;
+ {
+ jmethodID mid = env->GetMethodID(cls, "getMessage",
+ "()Ljava/lang/String;");
+ jmessage = (jstring) env->CallObjectMethod(t, mid);
+ }
- // Look in the thread local storage.
- JNIThreadData *data = JNIThreadData::getThreadData();
- return data->m_env;
+ JNIStringHolder class_name(jclass_name);
+ if (jmessage)
+ {
+ JNIStringHolder message(jmessage);
+ return apr_pstrcat(pool, class_name.c_str(), ": ", message.c_str(), NULL);
+ }
+ else
+ return class_name.pstrdup(pool);
+ // ### Conditionally add t.printStackTrace() to msg?
}
-/**
- * Check if a Java exception has been thrown.
- * @return is a Java exception has been thrown
- */
-bool JNIUtil::isJavaExceptionThrown()
+const char* exception_to_cstring(apr_pool_t* pool)
{
- JNIEnv *env = getEnv();
- if (env->ExceptionCheck())
+ const char *msg;
+ if (JNIUtil::getEnv()->ExceptionCheck())
{
- // Retrieving the exception removes it so we rethrow it here.
- jthrowable exp = env->ExceptionOccurred();
- env->ExceptionDescribe();
- env->Throw(exp);
- env->DeleteLocalRef(exp);
- setExceptionThrown();
- return true;
+ msg = known_exception_to_cstring(pool);
}
- return false;
+ else
+ {
+ msg = NULL;
+ }
+ return msg;
}
+} // anonymous namespace
const char *
JNIUtil::thrownExceptionToCString(SVN::Pool &in_pool)
{
- const char *msg = NULL;
- JNIEnv *env = getEnv();
- apr_pool_t *pool = in_pool.getPool();
- if (env->ExceptionCheck())
- {
- jthrowable t = env->ExceptionOccurred();
- jclass cls = env->GetObjectClass(t);
-
- jstring jclass_name;
- {
- jmethodID mid = env->GetMethodID(cls, "getClass", "()Ljava/lang/Class;");
- jobject clsobj = env->CallObjectMethod(t, mid);
- jclass basecls = env->GetObjectClass(clsobj);
- mid = env->GetMethodID(basecls, "getName", "()Ljava/lang/String;");
- jclass_name = (jstring) env->CallObjectMethod(clsobj, mid);
- }
-
- jstring jmessage;
- {
- jmethodID mid = env->GetMethodID(cls, "getMessage",
- "()Ljava/lang/String;");
- jmessage = (jstring) env->CallObjectMethod(t, mid);
- }
+ return exception_to_cstring(in_pool.getPool());
+}
- JNIStringHolder class_name(jclass_name);
- if (jmessage)
- {
- JNIStringHolder message(jmessage);
- msg = apr_pstrcat(pool,
- static_cast<const char*>(class_name), ": ",
- static_cast<const char*>(message), NULL);
- }
- else
- msg = class_name.pstrdup(pool);
- // ### Conditionally add t.printStackTrace() to msg?
- }
- return msg;
+svn_error_t*
+JNIUtil::checkJavaException(apr_status_t errorcode)
+{
+ if (!getEnv()->ExceptionCheck())
+ return SVN_NO_ERROR;
+ svn_error_t* err = svn_error_create(errorcode, NULL, NULL);
+ const char* const msg = known_exception_to_cstring(err->pool);
+ if (msg)
+ err->message = apr_psprintf(err->pool, _("Java exception: %s"), msg);
+ else
+ err->message = _("Java exception");
+ return err;
}
/**
@@ -710,25 +841,6 @@ jstring JNIUtil::makeJString(const char *txt)
return env->NewStringUTF(txt);
}
-void
-JNIUtil::setExceptionThrown(bool flag)
-{
- if (g_inInit)
- {
- // During global initialization, store any errors that occur
- // in a global variable (since thread-local storage may not
- // yet be available).
- g_initException = flag;
- }
- else
- {
- // When global initialization is complete, thread-local
- // storage should be available, so store the error there.
- JNIThreadData *data = JNIThreadData::getThreadData();
- data->m_exceptionThrown = flag;
- }
-}
-
/**
* Initialite the log file.
* @param level the log level
@@ -752,23 +864,6 @@ void JNIUtil::initLogFile(int level, jstring path)
}
/**
- * Returns a buffer to format error messages.
- * @return a buffer for formating error messages
- */
-char *JNIUtil::getFormatBuffer()
-{
- if (g_inInit) // during init -> use the global buffer
- return g_initFormatBuffer;
-
- // use the buffer in the thread local storage
- JNIThreadData *data = JNIThreadData::getThreadData();
- if (data == NULL) // if that does not exists -> use the global buffer
- return g_initFormatBuffer;
-
- return data->m_formatBuffer;
-}
-
-/**
* Returns the current log level.
* @return the log level
*/
@@ -819,6 +914,31 @@ jobject JNIUtil::createDate(apr_time_t time)
return ret;
}
+apr_time_t
+JNIUtil::getDate(jobject jdate)
+{
+ JNIEnv *env = getEnv();
+ jclass clazz = env->FindClass("java/util/Date");
+ if (isJavaExceptionThrown())
+ return 0;
+
+ static jmethodID mid = 0;
+ if (mid == 0)
+ {
+ mid = env->GetMethodID(clazz, "getTime", "()J");
+ if (isJavaExceptionThrown())
+ return 0;
+ }
+
+ jlong jmillis = env->CallLongMethod(jdate, mid);
+ if (isJavaExceptionThrown())
+ return 0;
+
+ env->DeleteLocalRef(clazz);
+
+ return jmillis * 1000;
+}
+
/**
* Create a Java byte array from an array of characters.
* @param data the character array
@@ -826,11 +946,9 @@ jobject JNIUtil::createDate(apr_time_t time)
*/
jbyteArray JNIUtil::makeJByteArray(const void *data, int length)
{
- if (data == NULL)
- {
- // a NULL will create no Java array
- return NULL;
- }
+ // a NULL will create no Java array
+ if (!data)
+ return NULL;
JNIEnv *env = getEnv();
@@ -861,62 +979,11 @@ jbyteArray JNIUtil::makeJByteArray(const void *data, int length)
*/
jbyteArray JNIUtil::makeJByteArray(const svn_string_t *str)
{
- return JNIUtil::makeJByteArray(str->data, static_cast<int>(str->len));
-}
-
-/**
- * Build the error message from the svn error into buffer. This
- * method calls itselft recursively for all the chained errors
- *
- * @param err the subversion error
- * @param depth the depth of the call, used for formating
- * @param parent_apr_err the apr of the previous level, used for formating
- * @param buffer the buffer where the formated error message will
- * be stored
- */
-void JNIUtil::assembleErrorMessage(svn_error_t *err, int depth,
- apr_status_t parent_apr_err,
- std::string &buffer)
-{
- // buffer for a single error message
- char errbuf[256];
-
- /* Pretty-print the error */
- /* Note: we can also log errors here someday. */
-
- /* When we're recursing, don't repeat the top-level message if its
- * the same as before. */
- if (depth == 0 || err->apr_err != parent_apr_err)
- {
- /* Is this a Subversion-specific error code? */
- if ((err->apr_err > APR_OS_START_USEERR)
- && (err->apr_err <= APR_OS_START_CANONERR))
- buffer.append(svn_strerror(err->apr_err, errbuf, sizeof(errbuf)));
- /* Otherwise, this must be an APR error code. */
- else
- {
- /* Messages coming from apr_strerror are in the native
- encoding, it's a good idea to convert them to UTF-8. */
- const char* utf8_message;
- apr_strerror(err->apr_err, errbuf, sizeof(errbuf));
- svn_error_t* utf8_err = svn_utf_cstring_to_utf8(
- &utf8_message, errbuf, err->pool);
- if (utf8_err)
- {
- /* Use fuzzy transliteration instead. */
- svn_error_clear(utf8_err);
- utf8_message = svn_utf_cstring_from_utf8_fuzzy(errbuf, err->pool);
- }
- buffer.append(utf8_message);
- }
- buffer.append("\n");
- }
- if (err->message)
- buffer.append(_("svn: ")).append(err->message).append("\n");
-
- if (err->child)
- assembleErrorMessage(err->child, depth + 1, err->apr_err, buffer);
+ // a NULL will create no Java array
+ if (!str)
+ return NULL;
+ return JNIUtil::makeJByteArray(str->data, static_cast<int>(str->len));
}
/**
@@ -936,8 +1003,6 @@ void JNIUtil::throwNullPointerException(const char *message)
return;
env->ThrowNew(clazz, message);
- setExceptionThrown();
- env->DeleteLocalRef(clazz);
}
svn_error_t *JNIUtil::preprocessPath(const char *&path, apr_pool_t *pool)
@@ -996,3 +1061,91 @@ svn_error_t *JNIUtil::preprocessPath(const char *&path, apr_pool_t *pool)
return NULL;
}
+
+/* Tag to use on the apr_pool_t to store a WrappedException reference */
+static const char *WrapExceptionTag = "org.apache.subversion.JavaHL.svnerror";
+
+class WrappedException
+{
+ JNIEnv *m_env;
+ jthrowable m_exception;
+#ifdef SVN_DEBUG
+ bool m_fetched;
+#endif
+public:
+ WrappedException(JNIEnv *env)
+ {
+ m_env = env;
+
+ // Fetch exception inside local frame
+ jthrowable exceptionObj = env->ExceptionOccurred();
+
+ // Now clear exception status
+ env->ExceptionClear();
+
+ // As adding a reference in exception state fails
+ m_exception = static_cast<jthrowable>(env->NewGlobalRef(exceptionObj));
+
+#ifdef SVN_DEBUG
+ m_fetched = false;
+#endif
+ }
+
+ static jthrowable get_exception(apr_pool_t *pool)
+ {
+ void *data;
+ if (! apr_pool_userdata_get(&data, WrapExceptionTag, pool))
+ {
+ WrappedException *we = reinterpret_cast<WrappedException *>(data);
+
+ if (we)
+ {
+#ifdef SVN_DEBUG
+ we->m_fetched = TRUE;
+#endif
+ // Create reference in local frame, as the pool will be cleared
+ return static_cast<jthrowable>(
+ we->m_env->NewLocalRef(we->m_exception));
+ }
+ }
+ return NULL;
+ }
+
+private:
+ ~WrappedException()
+ {
+#ifdef SVN_DEBUG
+ if (!m_fetched)
+ SVN_DBG(("Cleared svn_error_t * before Java exception was fetched"));
+#endif
+ m_env->DeleteGlobalRef(m_exception);
+ }
+public:
+ static apr_status_t cleanup(void *data)
+ {
+ WrappedException *we = reinterpret_cast<WrappedException *>(data);
+
+ delete we;
+ return APR_SUCCESS;
+ }
+};
+
+svn_error_t* JNIUtil::wrapJavaException()
+{
+ if (!isExceptionThrown())
+ return SVN_NO_ERROR;
+
+ svn_error_t *err = svn_error_create(SVN_ERR_JAVAHL_WRAPPED, NULL,
+ "Wrapped Java Exception");
+ apr_pool_userdata_set(new WrappedException(getEnv()), WrapExceptionTag,
+ WrappedException::cleanup, err->pool);
+ return err;
+}
+
+jthrowable JNIUtil::unwrapJavaException(const svn_error_t *err)
+{
+ if (!err)
+ return NULL;
+ return
+ WrappedException::get_exception(err->pool);
+}