blob: 64b532f21c24151efe15da132bd286635177042e [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <cstring>
#include <pthread.h>
#include <algorithm>
#include <sys/stat.h>
#include <dirent.h>
#include <dlfcn.h>
#include <unistd.h>
#include "ignite/common/utils.h"
#include "ignite/common/fixed_size_array.h"
#include "ignite/jni/utils.h"
#include "ignite/jni/java.h"
using namespace ignite::common;
using namespace ignite::jni::java;
namespace ignite
{
namespace jni
{
const char* JAVA_HOME = "JAVA_HOME";
const char* JAVA_DLL1 = "/jre/lib/amd64/server/libjvm.so";
const char* JAVA_DLL2 = "/lib/server/libjvm.so";
const char* JAVA_DLL_DARWIN = "libjvm.dylib";
const char* IGNITE_HOME = "IGNITE_HOME";
const char* IGNITE_NATIVE_TEST_CLASSPATH = "IGNITE_NATIVE_TEST_CLASSPATH";
/** Excluded modules from test classpath. */
const char* TEST_EXCLUDED_MODULES[] = { "rest-http" };
/** Key indicating that the thread is attached. */
static pthread_key_t attachKey;
/** Helper to ensure that attach key is allocated only once. */
static pthread_once_t attachKeyInit = PTHREAD_ONCE_INIT;
void DestroyAttachKey(void* key)
{
delete reinterpret_cast<AttachHelper*>(key);
}
void AllocateAttachKey()
{
pthread_key_create(&attachKey, DestroyAttachKey);
}
AttachHelper::~AttachHelper()
{
JniContext::Detach();
}
void AttachHelper::OnThreadAttach()
{
pthread_once(&attachKeyInit, AllocateAttachKey);
void* val = pthread_getspecific(attachKey);
if (!val)
pthread_setspecific(attachKey, new AttachHelper());
}
/**
* Checks if the path looks like binary release home directory.
* Internally checks for presence of some directories, that are
* @return @c true if the path looks like binary release home directory.
*/
bool LooksLikeBinaryReleaseHome(const std::string& path)
{
static const char* PROBE_CORE_LIB = "/libs/ignite-core*.jar";
std::string coreLibProbe = path + PROBE_CORE_LIB;
return FileExists(coreLibProbe);
}
/**
* Checks if the path looks like source release home directory.
* Internally checks for presence of core source directory.
* @return @c true if the path looks like binary release home directory.
*/
bool LooksLikeSourceReleaseHome(const std::string& path)
{
static const char* PROBE_CORE_SOURCE = "/modules/core/src/main/java/org/apache/ignite";
std::string coreSourcePath = path + PROBE_CORE_SOURCE;
return IsValidDirectory(coreSourcePath);
}
/**
* Helper function for Ignite home resolution.
* Goes upwards in directory hierarchy and checks whether certain
* folders exist in the path.
*
* @param path Path to evaluate.
* @return res Resolved directory. Empty string if not found.
*/
std::string ResolveIgniteHome0(const std::string& path)
{
if (!IsValidDirectory(path))
return std::string();
// Remove trailing slashes, otherwise we will have an infinite loop.
size_t last = path.find_last_not_of("/ ");
if (last == std::string::npos)
return std::string();
std::string path0(path, 0, last + 1);
if (LooksLikeBinaryReleaseHome(path0) || LooksLikeSourceReleaseHome(path0))
return path0;
// Evaluate parent directory.
size_t slashPos = path0.find_last_of("/");
if (slashPos == std::string::npos)
return std::string();
std::string parent(path0, 0, slashPos);
return ResolveIgniteHome0(parent);
}
/**
* Create classpath picking JARs from the given path.
*
* @path Path.
* @return Classpath;
*/
std::string ClasspathJars(const std::string& path)
{
std::string res;
DIR* dir = opendir(path.c_str());
if (dir)
{
struct dirent* entry;
while ((entry = readdir(dir)) != NULL)
{
char *dot = strrchr(entry->d_name, '.');
if (dot && !strcmp(dot, ".jar"))
{
res.append(path);
res.append("/");
res.append(entry->d_name);
res.append(":");
}
}
closedir(dir);
}
return res;
}
/**
* Check if path corresponds to excluded module.
*
* @path Path.
* @return True if path should be excluded.
*/
bool IsExcludedModule(const std::string& path) {
std::string lower_path = path;
std::transform(path.begin(), path.end(), lower_path.begin(), ::tolower);
for (size_t i = 0; i < sizeof(TEST_EXCLUDED_MODULES) / sizeof(char*); i++) {
if (lower_path.find(TEST_EXCLUDED_MODULES[i]) != std::string::npos)
return true;
}
return false;
}
/**
* Create classpath picking compiled classes from the given path.
*
* @path Path.
* @return Classpath;
*/
std::string ClasspathExploded(const std::string& path, bool down)
{
std::string res;
if (FileExists(path) && !IsExcludedModule(path))
{
// 1. Append "target\classes".
std::string classesPath = path + "/target/classes";
if (FileExists(classesPath)) {
res += classesPath;
res += ":";
}
// 2. Append "target\test-classes"
std::string testClassesPath = path + "/target/test-classes";
if (FileExists(testClassesPath)) {
res += testClassesPath;
res += ":";
}
// 3. Append "target\libs"
std::string libsPath = path + "/target/libs";
if (FileExists(libsPath)) {
std::string libsCp = ClasspathJars(libsPath);
res += libsCp;
}
// 4. Do the same for child if needed.
if (down)
{
DIR* dir = opendir(path.c_str());
if (dir)
{
struct dirent* entry;
while ((entry = readdir(dir)) != NULL)
{
std::string entryPath = entry->d_name;
if (entryPath.compare(".") != 0 && entryPath.compare("..") != 0)
{
std::string entryFullPath = path + "/" + entryPath;
struct stat entryFullStat;
if (stat(entryFullPath.c_str(), &entryFullStat) != -1 && S_ISDIR(entryFullStat.st_mode))
{
std::string childCp = ClasspathExploded(entryFullPath, false);
res += childCp;
}
}
}
closedir(dir);
}
}
}
return res;
}
std::string CreateIgniteHomeClasspath(const std::string& home, bool forceTest)
{
std::string res = std::string();
// 1. Add exploded test directories.
if (forceTest)
{
std::string examplesPath = home + "/examples";
std::string examplesCp = ClasspathExploded(examplesPath, true);
res.append(examplesCp);
std::string modulesPath = home + "/modules";
std::string modulesCp = ClasspathExploded(modulesPath, true);
res.append(modulesCp);
}
// 2. Add regular jars from "libs" folder excluding "optional".
std::string libsPath = home + "/libs";
if (FileExists(libsPath))
{
res.append(ClasspathJars(libsPath));
// Append inner directories.
DIR* dir = opendir(libsPath.c_str());
if (dir)
{
struct dirent* entry;
while ((entry = readdir(dir)) != NULL)
{
std::string entryPath = entry->d_name;
if (entryPath.compare(".") != 0 && entryPath.compare("..") != 0 &&
entryPath.compare("optional") != 0)
{
std::string entryFullPath = libsPath;
entryFullPath.append("/");
entryFullPath.append(entryPath);
struct stat entryFullStat;
if (stat(entryFullPath.c_str(), &entryFullStat) != -1 &&
S_ISDIR(entryFullStat.st_mode))
res.append(ClasspathJars(entryFullPath));
}
}
closedir(dir);
}
}
// 3. Return.
return res;
}
std::string FindJvmLibrary(const std::string& path)
{
#ifdef __APPLE__
return JAVA_DLL_DARWIN;
#else
// If path is provided explicitly, then check only it.
if (!path.empty() && FileExists(path))
return path;
std::string javaEnv = GetEnv(JAVA_HOME);
if (!javaEnv.empty())
{
std::string javaDll = javaEnv + JAVA_DLL1;
if (FileExists(javaDll))
return javaDll;
javaDll = javaEnv + JAVA_DLL2;
if (FileExists(javaDll))
return javaDll;
}
return std::string();
#endif
}
bool LoadJvmLibrary(const std::string& path)
{
#ifdef __APPLE__
return RTLD_DEFAULT;
#else
void* hnd = dlopen(path.c_str(), RTLD_LAZY);
return hnd != NULL;
#endif
}
/**
* Create Ignite classpath based on user input and home directory.
*
* @param usrCp User's classpath.
* @param home Ignite home directory.
* @param forceTest Whether test classpath must be used.
* @return Classpath.
*/
std::string CreateIgniteClasspath(const std::string& usrCp, const std::string* home, bool forceTest)
{
// 1. Append user classpath if it exists.
std::string cp;
if (!usrCp.empty())
{
cp.append(usrCp);
if (*cp.rbegin() != ';')
cp.push_back(';');
}
// 2. Append home classpath if home is defined.
if (home)
{
std::string homeCp = CreateIgniteHomeClasspath(*home, forceTest);
cp.append(homeCp);
}
// 3. Return.
return cp;
}
/**
* Adds semicolon at the end of the path if needed.
* @param usrCp Classpath provided by user.
* @return Normalized classpath.
*/
std::string NormalizeClasspath(const std::string& usrCp)
{
if (usrCp.empty() || *usrCp.rbegin() == ';')
return usrCp;
return usrCp + ';';
}
std::string CreateIgniteClasspath(const std::string& usrCp, const std::string& home)
{
// 1. Append user classpath if it exists.
std::string cp = NormalizeClasspath(usrCp);
// 2. Append home classpath
if (!home.empty())
{
std::string env = GetEnv(IGNITE_NATIVE_TEST_CLASSPATH, "false");
bool forceTest = ToLower(env) == "true";
std::string homeCp = CreateIgniteHomeClasspath(home, forceTest);
cp.append(homeCp);
}
// 3. Return.
return cp;
}
std::string ResolveIgniteHome(const std::string& path)
{
// 1. Check passed argument.
if (IsValidDirectory(path))
return path;
// 2. Check environment variable.
std::string home = GetEnv(IGNITE_HOME);
if (IsValidDirectory(home))
return home;
// 3. Check current work dir.
FixedSizeArray<char> curDir(1024 * 16);
char* res = getcwd(curDir.GetData(), curDir.GetSize());
if (!res)
return std::string();
std::string curDirStr(curDir.GetData());
return ResolveIgniteHome0(curDirStr);
}
}
}