-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: native lib jar can be downloaded from remote repo
- Loading branch information
Showing
6 changed files
with
242 additions
and
2 deletions.
There are no files selected for viewing
74 changes: 74 additions & 0 deletions
74
helm-java/src/test/java/com/marcnuri/helm/NativeLibraryTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
package com.marcnuri.helm; | ||
|
||
import com.marcnuri.helm.jni.NativeLibrary; | ||
import org.junit.jupiter.api.Disabled; | ||
import org.junit.jupiter.api.Test; | ||
|
||
import java.net.URLClassLoader; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | ||
|
||
class NativeLibraryTest { | ||
|
||
@Test | ||
void getInstanceFromClassPath() { | ||
System.setProperty("com.marcnuri.jkube-helm.skipRemoteJar", "true"); | ||
try { | ||
NativeLibrary nativeLibrary = NativeLibrary.getInstance(); | ||
assertThat(nativeLibrary).isNotNull(); | ||
} finally { | ||
System.clearProperty("com.marcnuri.jkube-helm.skipRemoteJar"); | ||
} | ||
} | ||
|
||
@Test | ||
void getSnapshotInstanceFromRemoteJar() { | ||
final ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader(); | ||
// Use a PUBLISHED snapshot version | ||
System.setProperty("com.marcnuri.jkube-helm.version", "0.0-SNAPSHOT"); | ||
System.setProperty("com.marcnuri.jkube-helm.forceUpdate", "true"); | ||
try { | ||
Thread.currentThread().setContextClassLoader(new URLClassLoader(new java.net.URL[0], null)); | ||
assertThat(NativeLibrary.serviceProviderLibrary(null)).isNull(); | ||
NativeLibrary nativeLibrary = NativeLibrary.getInstance(); | ||
assertThat(nativeLibrary).isNotNull(); | ||
} finally { | ||
Thread.currentThread().setContextClassLoader(currentClassLoader); | ||
System.clearProperty("com.marcnuri.jkube-helm.version"); | ||
} | ||
} | ||
|
||
@Test | ||
@Disabled("Can only be enabled once we do an initial stable release") // TODO | ||
void getReleaseInstanceFromRemoteJar() { | ||
final ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader(); | ||
// Use a PUBLISHED snapshot version | ||
System.setProperty("com.marcnuri.jkube-helm.version", "0.0.0"); | ||
System.setProperty("com.marcnuri.jkube-helm.forceUpdate", "true"); | ||
try { | ||
Thread.currentThread().setContextClassLoader(new URLClassLoader(new java.net.URL[0], null)); | ||
assertThat(NativeLibrary.serviceProviderLibrary(null)).isNull(); | ||
NativeLibrary nativeLibrary = NativeLibrary.getInstance(); | ||
assertThat(nativeLibrary).isNotNull(); | ||
} finally { | ||
Thread.currentThread().setContextClassLoader(currentClassLoader); | ||
System.clearProperty("com.marcnuri.jkube-helm.version"); | ||
} | ||
} | ||
|
||
@Test | ||
void getInstanceNotAvailable() { | ||
final ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader(); | ||
System.setProperty("com.marcnuri.jkube-helm.skipRemoteJar", "true"); | ||
try { | ||
Thread.currentThread().setContextClassLoader(new URLClassLoader(new java.net.URL[0], null)); | ||
assertThatThrownBy(NativeLibrary::getInstance) | ||
.isInstanceOf(IllegalStateException.class) | ||
.hasMessage("No NativeLibrary implementation found, please add one of the supported dependencies to your project"); | ||
} finally { | ||
System.clearProperty("com.marcnuri.jkube-helm.skipRemoteJar"); | ||
Thread.currentThread().setContextClassLoader(currentClassLoader); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
127 changes: 127 additions & 0 deletions
127
lib/api/src/main/java/com/marcnuri/helm/jni/RemoteJarLoader.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
package com.marcnuri.helm.jni; | ||
|
||
import org.w3c.dom.Document; | ||
import org.xml.sax.SAXException; | ||
|
||
import javax.xml.parsers.DocumentBuilderFactory; | ||
import javax.xml.parsers.ParserConfigurationException; | ||
import javax.xml.xpath.XPath; | ||
import javax.xml.xpath.XPathExpressionException; | ||
import javax.xml.xpath.XPathFactory; | ||
import java.io.FileOutputStream; | ||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import java.net.URL; | ||
import java.net.URLClassLoader; | ||
import java.nio.channels.Channels; | ||
import java.nio.channels.FileChannel; | ||
import java.nio.channels.ReadableByteChannel; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.nio.file.Paths; | ||
import java.util.Properties; | ||
|
||
class RemoteJarLoader { | ||
|
||
private static final String TEMP_DIR = "java.io.tmpdir"; | ||
private static final String VERSION = "com.marcnuri.jkube-helm.version"; | ||
private static final String GROUP_ID = "com.marcnuri.jkube-helm.groupId"; | ||
private static final String SNAPSHOTS = "com.marcnuri.jkube-helm.repository.snapshots"; | ||
private static final String RELEASES = "com.marcnuri.jkube-helm.repository.releases"; | ||
// For testing | ||
private static final String FORCE_UPDATE = "com.marcnuri.jkube-helm.forceUpdate"; | ||
private static final String SKIP = "com.marcnuri.jkube-helm.skipRemoteJar"; | ||
|
||
private RemoteJarLoader() { | ||
} | ||
|
||
static ClassLoader remoteJar() { | ||
// For testing | ||
if (System.getProperty(SKIP) != null) { | ||
return null; | ||
} | ||
final Properties nativeLibraries = new Properties(); | ||
try (final InputStream stream = NativeLibrary.class.getResourceAsStream("/META-INF/native-libraries.properties")) { | ||
nativeLibraries.load(stream); | ||
final String version; | ||
// Version can be overridden (for testing) using a system property | ||
if (System.getProperty(VERSION) != null) { | ||
version = System.getProperty(VERSION); | ||
} else { | ||
version = nativeLibraries.getProperty(VERSION); | ||
} | ||
final String groupId = nativeLibraries.getProperty(GROUP_ID); | ||
final boolean isSnapshot = version.endsWith("-SNAPSHOT"); | ||
final String repository = isSnapshot ? | ||
nativeLibraries.getProperty(SNAPSHOTS) : | ||
nativeLibraries.getProperty(RELEASES); | ||
final String osName = osName(); | ||
final String archName = archName(); | ||
if (osName != null && archName != null) { | ||
final String groupUrl = repository + "/" + groupId.replace('.', '/') + "/" + osName + "-" + archName + "/" + version; | ||
final String jarName; | ||
if (isSnapshot) { | ||
jarName = latestSnapshot(version, osName, archName, groupUrl); | ||
} else { | ||
jarName = osName + "-" + archName + "-" + version + ".jar"; | ||
} | ||
final URL jarUrl = new URL(groupUrl + "/" + jarName); | ||
final Path jarFile = Paths.get(System.getProperty(TEMP_DIR), jarName); | ||
return new URLClassLoader(new URL[]{cache(jarUrl, jarFile)}); | ||
} | ||
} catch (IOException | ParserConfigurationException | SAXException | XPathExpressionException exception) { | ||
// NO OP | ||
} | ||
return null; | ||
} | ||
|
||
private static String latestSnapshot(String version, String osName, String archName, String groupUrl) | ||
throws IOException, ParserConfigurationException, SAXException, XPathExpressionException { | ||
final String metadataXml = groupUrl + "/maven-metadata.xml"; | ||
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); | ||
factory.setFeature("http://xml.org/sax/features/external-general-entities", false); | ||
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); | ||
final Document xml = factory.newDocumentBuilder().parse(metadataXml); | ||
final XPath xPath = XPathFactory.newInstance().newXPath(); | ||
final String versionFragment = version.replace("-SNAPSHOT", ""); | ||
final String timestamp = xPath.evaluate("//metadata/versioning/snapshot/timestamp", xml); | ||
final String buildNumber = xPath.evaluate("//metadata/versioning/snapshot/buildNumber", xml); | ||
return osName + "-" + archName + "-" + versionFragment + "-" + timestamp + "-" + buildNumber + ".jar"; | ||
} | ||
|
||
private static URL cache(URL jarUrl, Path jarFile) throws IOException { | ||
if (System.getProperty(FORCE_UPDATE) != null || !jarFile.toFile().exists() || !jarFile.toFile().isFile()) { | ||
Files.deleteIfExists(jarFile); | ||
try ( | ||
ReadableByteChannel inChannel = Channels.newChannel(jarUrl.openStream()); | ||
FileOutputStream fos = new FileOutputStream(jarFile.toFile()); | ||
FileChannel outChannel = fos.getChannel() | ||
) { | ||
outChannel.transferFrom(inChannel, 0, Long.MAX_VALUE); | ||
} | ||
} | ||
return jarFile.toUri().toURL(); | ||
} | ||
|
||
private static String osName() { | ||
final String osName = System.getProperty("os.name").toLowerCase(); | ||
if (osName.contains("win")) { | ||
return "windows"; | ||
} else if (osName.contains("mac")) { | ||
return "darwin"; | ||
} else if (osName.contains("nux") || osName.contains("nix") || osName.contains("aix")) { | ||
return "linux"; | ||
} | ||
return null; | ||
} | ||
|
||
private static String archName() { | ||
final String arch = System.getProperty("os.arch"); | ||
if (arch.equals("amd64") || arch.equals("x86_64")) { | ||
return "amd64"; | ||
} else if (arch.equals("arch64")) { | ||
return "arm64"; | ||
} | ||
return null; | ||
} | ||
} |
4 changes: 4 additions & 0 deletions
4
lib/api/src/main/resources-filtered/META-INF/native-libraries.properties
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
com.marcnuri.jkube-helm.groupId=${project.groupId} | ||
com.marcnuri.jkube-helm.version=${project.version} | ||
com.marcnuri.jkube-helm.repository.snapshots=${com.marcnuri.jkube-helm.repository.snapshots} | ||
com.marcnuri.jkube-helm.repository.releases=${com.marcnuri.jkube-helm.repository.snapshots} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters