Skip to content

Instantly share code, notes, and snippets.

@krisfoster
Created May 11, 2021 13:47
Show Gist options
  • Save krisfoster/4c1604237b0ba3244711feda50d4e911 to your computer and use it in GitHub Desktop.
Save krisfoster/4c1604237b0ba3244711feda50d4e911 to your computer and use it in GitHub Desktop.
package org.example.graal.features;
import com.oracle.svm.core.SubstrateUtil;
import com.oracle.svm.core.annotate.*;
import com.oracle.svm.hosted.FeatureImpl;
import com.oracle.svm.hosted.config.ReflectionRegistryAdapter;
import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.nativeimage.hosted.Feature;
import org.graalvm.nativeimage.hosted.RuntimeReflection;
import org.graalvm.nativeimage.impl.ReflectionRegistry;
import org.graalvm.nativeimage.impl.RuntimeReflectionSupport;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
// TODO: description and copyright
//https://github.com/spring-projects-experimental/spring-native/issues/620
@AutomaticFeature
public class LiquibaseFeature implements Feature {
private static final String[] REQUIRED_CLASSES = {
"liquibase.database.Database",
"liquibase.lockservice.LockService",
"liquibase.executor.Executor",
"liquibase.snapshot.SnapshotGenerator",
"liquibase.license.LicenseService",
"liquibase.sqlgenerator.SqlGenerator",
"liquibase.datatype.LiquibaseDataType",
"liquibase.parser.ChangeLogParser",
"liquibase.change.Change",
"liquibase.precondition.Precondition",
"liquibase.parser.NamespaceDetails",
"liquibase.changelog.ChangeLogHistoryService",
"liquibase.diff.compare.DatabaseObjectComparator"
};
@Override
public void beforeAnalysis(BeforeAnalysisAccess access) {
FeatureImpl.BeforeAnalysisAccessImpl impl = (FeatureImpl.BeforeAnalysisAccessImpl) access;
CachedClassesInPackagesStore store = new CachedClassesInPackagesStore();
log("Registering required Liquibase services.");
for (String clazzName : REQUIRED_CLASSES) {
log("Registering service: " + clazzName);
Class<?> clazz = impl.findClassByName(clazzName);
if (clazz == null) {
log("Unable to find liquibase required class: " + clazzName);
System.exit(1);
}
@SuppressWarnings("unchecked")
List<Class<?>> subClasses = impl.findSubclasses((Class<Object>) clazz);
subClasses = subClasses.stream()
.filter(subClazz -> !Modifier.isAbstract(subClazz.getModifiers()))
.filter(subClazz -> !subClazz.isInterface())
.collect(Collectors.toList());
ReflectionRegistry registry = ImageSingletons.lookup(RuntimeReflectionSupport.class);
ReflectionRegistryAdapter reflectAdapter = new ReflectionRegistryAdapter(registry, impl.getImageClassLoader());
for (Class<?> subClazz: subClasses) {
log("Registered subclass: " + subClazz + " [" + clazzName + "]");
store.cache.compute(subClazz.getPackage().getName(), (clazzPackage, clazzList) -> {
List<Class<?>> targetClazzList = clazzList != null ? clazzList : new ArrayList<>();
targetClazzList.add(subClazz);
reflectAdapter.registerAllConstructors(subClazz);
reflectAdapter.registerDeclaredMethods(subClazz);
reflectAdapter.registerPublicMethods(subClazz);
return targetClazzList;
});
}
}
// fill up the cache store
ImageSingletons.add(CachedClassesInPackagesStore.class, store);
}
static void log(String message) {
System.out.println("[INTERNAL LIQUIBASE FEATURE]: " + message);
}
}
class CachedClassesInPackagesStore {
public Map<String, List<Class<?>>> cache = new HashMap<>();
}
@TargetClass(className="org.apache.commons.logging.Log")
final class Target_org_apache_commons_logging_Log {
@Alias
public native void info(Object message);
}
@SuppressWarnings("unused")
@TargetClass(org.springframework.boot.liquibase.SpringPackageScanClassResolver.class)
//className="org.springframework.boot.liquibase.SpringPackageScanClassResolver"
final class Target_org_springframework_boot_liquibase_SpringPackageScanClassResolver {
@Alias
private Target_org_apache_commons_logging_Log logger;
@Substitute
protected void findAllClasses(String packageName, ClassLoader loader) {
CachedClassesInPackagesStore store = ImageSingletons.lookup(CachedClassesInPackagesStore.class);
logger.info("Searching for classes in package: " + packageName);
for (String fullyQualifiedPackageName : store.cache.keySet()) {
if (fullyQualifiedPackageName.startsWith(packageName.replace("/", "."))) {
logger.info("Scanning package: " + fullyQualifiedPackageName);
int clazzCount = 0;
for (Class<?> clazz: store.cache.get(fullyQualifiedPackageName)) {
logger.info("Found class: " + clazz.getName());
clazzCount += 1;
SubstrateUtil.cast(this, Target_liquibase_servicelocator_DefaultPackageScanClassResolver.class).addFoundClass(clazz);
}
logger.info("Total classes found in " + fullyQualifiedPackageName + ": " + clazzCount);
} else {
logger.info("Skipping package: " + fullyQualifiedPackageName);
}
}
}
}
@TargetClass(liquibase.servicelocator.DefaultPackageScanClassResolver.class)
final class Target_liquibase_servicelocator_DefaultPackageScanClassResolver {
@Alias
protected native void addFoundClass(Class<?> type);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment