/*
* 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.
*/
package org.apache.flink.contrib.streaming.state;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.rocksdb.RocksDB;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import static org.junit.Assert.*;
/**
* This test validates that the RocksDB JNI library loading works properly
* in the presence of the RocksDB code being loaded dynamically via reflection.
* That can happen when RocksDB is in the user code JAR, or in certain test setups.
*/
public class RocksDbMultiClassLoaderTest {
@Rule
public final TemporaryFolder tmp = new TemporaryFolder();
@Test
public void testTwoSeparateClassLoaders() throws Exception {
// collect the libraries / class folders with RocksDB related code: the state backend and RocksDB itself
final URL codePath1 = RocksDBStateBackend.class.getProtectionDomain().getCodeSource().getLocation();
final URL codePath2 = RocksDB.class.getProtectionDomain().getCodeSource().getLocation();
final ClassLoader parent = getClass().getClassLoader();
final ClassLoader loader1 = new ChildFirstClassLoader(new URL[] { codePath1, codePath2 }, parent);
final ClassLoader loader2 = new ChildFirstClassLoader(new URL[] { codePath1, codePath2 }, parent);
final String className = RocksDBStateBackend.class.getName();
final Class<?> clazz1 = Class.forName(className, false, loader1);
final Class<?> clazz2 = Class.forName(className, false, loader2);
assertNotEquals("Test broken - the two reflectively loaded classes are equal", clazz1, clazz2);
final Object instance1 = clazz1.getConstructor(String.class).newInstance(tmp.newFolder().toURI().toString());
final Object instance2 = clazz2.getConstructor(String.class).newInstance(tmp.newFolder().toURI().toString());
final String tempDir = tmp.newFolder().getAbsolutePath();
final Method meth1 = clazz1.getDeclaredMethod("ensureRocksDBIsLoaded", String.class);
final Method meth2 = clazz2.getDeclaredMethod("ensureRocksDBIsLoaded", String.class);
meth1.setAccessible(true);
meth2.setAccessible(true);
// if all is well, these methods can both complete successfully
meth1.invoke(instance1, tempDir);
meth2.invoke(instance2, tempDir);
}
// ------------------------------------------------------------------------
/**
* A variant of the URLClassLoader that first loads from the URLs and only after that from the parent.
*/
private static final class ChildFirstClassLoader extends URLClassLoader {
private final ClassLoader parent;
public ChildFirstClassLoader(URL[] urls, ClassLoader parent) {
super(urls, null);
this.parent = parent;
}
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
// first try to load from the URLs
// because the URLClassLoader's parent is null, this cannot implicitly load from the parent
try {
return super.findClass(name);
}
catch (ClassNotFoundException e) {
// not in the URL, check the parent
return parent.loadClass(name);
}
}
}
}