1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package de.softwareforge.testing.postgres.embedded;
16
17 import static com.google.common.base.Preconditions.checkNotNull;
18 import static com.google.common.base.Preconditions.checkState;
19 import static java.lang.String.format;
20
21 import de.softwareforge.testing.maven.MavenArtifactLoader;
22
23 import jakarta.annotation.Nonnull;
24 import java.io.File;
25 import java.io.FilterInputStream;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.io.UncheckedIOException;
29 import java.nio.charset.StandardCharsets;
30 import java.util.Objects;
31 import java.util.function.Supplier;
32 import java.util.jar.JarEntry;
33 import java.util.jar.JarFile;
34
35 import com.google.common.base.Suppliers;
36 import com.google.common.hash.HashCode;
37 import com.google.common.hash.Hashing;
38 import com.google.common.io.BaseEncoding;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42
43
44
45
46
47
48
49
50 public final class ZonkyIOPostgresLocator implements NativeBinaryLocator {
51
52 private static final String ZONKY_GROUP_ID = "io.zonky.test.postgres";
53 private static final String ZONKY_ARTIFACT_ID_TEMPLATE = "embedded-postgres-binaries-%s-%s";
54
55 private static final Logger LOG = LoggerFactory.getLogger(ZonkyIOPostgresLocator.class);
56
57 private static final boolean PREFER_NATIVE = Boolean.getBoolean("pg-embedded.prefer-native");
58
59 private final String architecture;
60 private final String os;
61 private final String serverVersion;
62
63 private final MavenArtifactLoader artifactLoader = new MavenArtifactLoader();
64
65 private final Supplier<File> fileSupplier = Suppliers.memoize(this::loadArtifact);
66
67 ZonkyIOPostgresLocator(String serverVersion) {
68 this.serverVersion = checkNotNull(serverVersion, "serverVersion is null");
69
70 this.os = computeOS();
71 this.architecture = computeTarXzArchitectureName();
72 LOG.debug(format("Detected a %s %s system, using PostgreSQL version %s/%s", EmbeddedUtil.OS_ARCH, os, serverVersion, architecture));
73 }
74
75 @Override
76 public InputStream getInputStream() throws IOException {
77 try {
78 File artifactFile = fileSupplier.get();
79 return createJarStream(artifactFile);
80 } catch (UncheckedIOException e) {
81 throw e.getCause();
82 }
83 }
84
85 @Override
86 @Nonnull
87 public String getIdentifier() throws IOException {
88
89 try {
90 File artifactFile = fileSupplier.get();
91 HashCode hashCode = Hashing.murmur3_128().hashString(artifactFile.getAbsolutePath(), StandardCharsets.UTF_8);
92 return INSTALL_DIRECTORY_PREFIX + BaseEncoding.base16().encode(hashCode.asBytes());
93 } catch (UncheckedIOException e) {
94 throw e.getCause();
95 }
96 }
97
98 private File loadArtifact() {
99 try {
100 String artifactId = format(ZONKY_ARTIFACT_ID_TEMPLATE, this.os, computeJarArchitectureName());
101
102
103 if (EmbeddedUtil.IS_ALPINE_LINUX) {
104 artifactId += "-alpine";
105 }
106
107 String version = artifactLoader.builder(ZONKY_GROUP_ID, artifactId)
108 .partialMatch(serverVersion)
109 .includeSnapshots(false)
110 .findBestMatch()
111 .orElseThrow(() -> new IllegalStateException(format("Could not download artifact for Zonky Postgres %s", serverVersion)));
112
113 File file = artifactLoader.getArtifactFile(ZONKY_GROUP_ID, artifactId, version);
114 checkState(file != null && file.exists(), "Could not locate artifact file for %s:%s", artifactId, version);
115 LOG.info(format("Using PostgreSQL version %s (%s)", version, architecture));
116 return file;
117 } catch (IOException e) {
118 throw new UncheckedIOException(e);
119 }
120 }
121
122 private InputStream createJarStream(File file) {
123 try {
124 JarFile jar = new JarFile(file);
125 String entryName = format("postgres-%s-%s", computeOS(), computeTarXzArchitectureName());
126
127
128 if (EmbeddedUtil.IS_ALPINE_LINUX) {
129 entryName += "-alpine_linux";
130 }
131
132 JarEntry jarEntry = jar.getJarEntry(entryName + ".txz");
133 checkState(jarEntry != null, "Could not locate %s in the jar file (%s)", entryName, file.getAbsoluteFile());
134
135
136 return new FilterInputStream(jar.getInputStream(jarEntry)) {
137 @Override
138 public void close() throws IOException {
139 try {
140 super.close();
141 } finally {
142 jar.close();
143 }
144 }
145 };
146 } catch (IOException e) {
147 throw new UncheckedIOException(e);
148 }
149 }
150
151 @Override
152 public String toString() {
153 return format("ZonkyIO Stream locator for PostgreSQL (machine: %s os: %s, arch: %s, version: %s)",
154 EmbeddedUtil.OS_ARCH, os, architecture, serverVersion);
155 }
156
157 @Override
158 public boolean equals(Object o) {
159 if (this == o) {
160 return true;
161 }
162 if (o == null || getClass() != o.getClass()) {
163 return false;
164 }
165 ZonkyIOPostgresLocator that = (ZonkyIOPostgresLocator) o;
166 return architecture.equals(that.architecture) && os.equals(that.os) && serverVersion.equals(that.serverVersion);
167 }
168
169 @Override
170 public int hashCode() {
171 return Objects.hash(architecture, os, serverVersion);
172 }
173
174 private static String computeTarXzArchitectureName() {
175 String architecture = EmbeddedUtil.OS_ARCH;
176 if (EmbeddedUtil.IS_ARCH_X86_64) {
177 architecture = "x86_64";
178 } else if (EmbeddedUtil.IS_ARCH_AARCH64) {
179 if (!PREFER_NATIVE && EmbeddedUtil.IS_OS_MAC) {
180
181 architecture = "x86_64";
182 } else {
183 architecture = "arm_64";
184 }
185 } else if (EmbeddedUtil.IS_ARCH_AARCH32) {
186 architecture = "arm_32";
187 }
188 return architecture;
189 }
190
191 private static String computeJarArchitectureName() {
192 String architecture = EmbeddedUtil.OS_ARCH;
193 if (EmbeddedUtil.IS_ARCH_X86_64) {
194 architecture = "amd64";
195 } else if (EmbeddedUtil.IS_ARCH_AARCH64) {
196 if (!PREFER_NATIVE && EmbeddedUtil.IS_OS_MAC) {
197
198 architecture = "amd64";
199 } else {
200 architecture = "arm64v8";
201 }
202 } else if (EmbeddedUtil.IS_ARCH_AARCH32) {
203 architecture = "arm32v7";
204 }
205 return architecture;
206 }
207
208 private static String computeOS() {
209 String os = EmbeddedUtil.OS_NAME;
210 if (EmbeddedUtil.IS_OS_LINUX) {
211 os = "linux";
212 } else if (EmbeddedUtil.IS_OS_MAC) {
213 os = "darwin";
214 } else if (EmbeddedUtil.IS_OS_WINDOWS) {
215 os = "windows";
216 }
217 return os;
218 }
219 }