View Javadoc
1   /*
2    * Licensed under the Apache License, Version 2.0 (the "License");
3    * you may not use this file except in compliance with the License.
4    * You may obtain a copy of the License at
5    *
6    * http://www.apache.org/licenses/LICENSE-2.0
7    *
8    * Unless required by applicable law or agreed to in writing, software
9    * distributed under the License is distributed on an "AS IS" BASIS,
10   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11   * See the License for the specific language governing permissions and
12   * limitations under the License.
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 java.io.File;
24  import java.io.FilterInputStream;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.io.UncheckedIOException;
28  import java.nio.charset.StandardCharsets;
29  import java.util.Objects;
30  import java.util.function.Supplier;
31  import java.util.jar.JarEntry;
32  import java.util.jar.JarFile;
33  
34  import com.google.common.base.Suppliers;
35  import com.google.common.hash.HashCode;
36  import com.google.common.hash.Hashing;
37  import com.google.common.io.BaseEncoding;
38  import org.slf4j.Logger;
39  import org.slf4j.LoggerFactory;
40  
41  /**
42   * Resolves PostgreSQL archives from the Maven repository. Looks for the zonky.io artifacts located at
43   * <code>io.zonky.test.postgres:embedded-postgres-binaries-&lt;os&gt;-&lt;arch&gt;</code>.
44   * <p>
45   * See <a href="https://github.com/zonkyio/embedded-postgres-binaries">The Zonky IO github page</a> for more details.
46   */
47  public final class ZonkyIOPostgresLocator implements NativeBinaryLocator {
48  
49      private static final String ZONKY_GROUP_ID = "io.zonky.test.postgres";
50      private static final String ZONKY_ARTIFACT_ID_TEMPLATE = "embedded-postgres-binaries-%s-%s";
51  
52      public static final Logger LOG = LoggerFactory.getLogger(ZonkyIOPostgresLocator.class);
53  
54      private static final boolean PREFER_NATIVE = Boolean.getBoolean("pg-embedded.prefer-native");
55  
56      private final String architecture;
57      private final String os;
58      private final String serverVersion;
59  
60      private final MavenArtifactLoader artifactLoader = new MavenArtifactLoader();
61  
62      private final Supplier<File> fileSupplier = Suppliers.memoize(this::loadArtifact);
63  
64      ZonkyIOPostgresLocator(String serverVersion) {
65          this.serverVersion = checkNotNull(serverVersion, "serverVersion is null");
66  
67          this.os = computeOS();
68          this.architecture = computeTarXzArchitectureName();
69          LOG.debug(format("Detected a %s %s system, using PostgreSQL version %s", architecture, os, serverVersion));
70      }
71  
72      @Override
73      public InputStream getInputStream() throws IOException {
74          try {
75              File artifactFile = fileSupplier.get();
76              return createJarStream(artifactFile);
77          } catch (UncheckedIOException e) {
78              throw e.getCause();
79          }
80      }
81  
82      @Override
83      public String getIdentifier() throws IOException {
84          // the optimized identifier computation saves ~ 1% CPU according to the profiler
85          try {
86              File artifactFile = fileSupplier.get();
87              HashCode hashCode = Hashing.murmur3_128().hashString(artifactFile.getAbsolutePath(), StandardCharsets.UTF_8);
88              return INSTALL_DIRECTORY_PREFIX + BaseEncoding.base16().encode(hashCode.asBytes());
89          } catch (UncheckedIOException e) {
90              throw e.getCause();
91          }
92      }
93  
94      private File loadArtifact() {
95          try {
96              String artifactId = format(ZONKY_ARTIFACT_ID_TEMPLATE, this.os, computeJarArchitectureName());
97  
98              // alpine hack
99              if (EmbeddedUtil.IS_ALPINE_LINUX) {
100                 artifactId += "-alpine";
101             }
102 
103             String version = artifactLoader.findLatestVersion(ZONKY_GROUP_ID, artifactId, serverVersion);
104             File file = artifactLoader.getArtifactFile(ZONKY_GROUP_ID, artifactId, version);
105             checkState(file != null && file.exists(), "Could not locate artifact file for %s:%s", artifactId, version);
106             LOG.info(format("Using PostgreSQL version %s", version));
107             return file;
108         } catch (IOException e) {
109             throw new UncheckedIOException(e);
110         }
111     }
112 
113     private InputStream createJarStream(File file) {
114         try {
115             JarFile jar = new JarFile(file);
116             String entryName = format("postgres-%s-%s", computeOS(), computeTarXzArchitectureName());
117 
118             // alpine hack
119             if (EmbeddedUtil.IS_ALPINE_LINUX) {
120                 entryName += "-alpine_linux";
121             }
122 
123             JarEntry jarEntry = jar.getJarEntry(entryName + ".txz");
124             checkState(jarEntry != null, "Could not locate %s in the jar file (%s)", entryName, file.getAbsoluteFile());
125 
126             // When the input stream gets closed, close the jar file as well.
127             return new FilterInputStream(jar.getInputStream(jarEntry)) {
128                 @Override
129                 public void close() throws IOException {
130                     try {
131                         super.close();
132                     } finally {
133                         jar.close();
134                     }
135                 }
136             };
137         } catch (IOException e) {
138             throw new UncheckedIOException(e);
139         }
140     }
141 
142     @Override
143     public String toString() {
144         return format("ZonkyIO Stream locator for PostgreSQL (arch: %s os: %s, version: %s)", architecture, os, serverVersion);
145     }
146 
147     @Override
148     public boolean equals(Object o) {
149         if (this == o) {
150             return true;
151         }
152         if (o == null || getClass() != o.getClass()) {
153             return false;
154         }
155         ZonkyIOPostgresLocator/de/softwareforge/testing/postgres/embedded/ZonkyIOPostgresLocator.html#ZonkyIOPostgresLocator">ZonkyIOPostgresLocator that = (ZonkyIOPostgresLocator) o;
156         return architecture.equals(that.architecture) && os.equals(that.os) && serverVersion.equals(that.serverVersion);
157     }
158 
159     @Override
160     public int hashCode() {
161         return Objects.hash(architecture, os, serverVersion);
162     }
163 
164     private static String computeTarXzArchitectureName() {
165         String architecture = EmbeddedUtil.OS_ARCH;
166         if (EmbeddedUtil.IS_ARCH_X86_64) {
167             architecture = "x86_64";  // Zonky uses x86_64
168         } else if (EmbeddedUtil.IS_ARCH_AARCH64) {
169             if (!PREFER_NATIVE && EmbeddedUtil.IS_OS_MAC) {
170                 // Mac binaries are fat binaries stored as x86_64
171                 architecture = "x86_64";
172             } else {
173                 architecture = "arm_64";
174             }
175         } else if (EmbeddedUtil.IS_ARCH_AARCH32) {
176             architecture = "arm_32";
177         }
178         return architecture;
179     }
180 
181     private static String computeJarArchitectureName() {
182         String architecture = EmbeddedUtil.OS_ARCH;
183         if (EmbeddedUtil.IS_ARCH_X86_64) {
184             architecture = "amd64";  // Zonky uses amd64 for the jar name
185         } else if (EmbeddedUtil.IS_ARCH_AARCH64) {
186             if (!PREFER_NATIVE && EmbeddedUtil.IS_OS_MAC) {
187                 // Mac binaries are fat binaries stored as amd64
188                 architecture = "amd64";
189             } else {
190                 architecture = "arm64v8";
191             }
192         } else if (EmbeddedUtil.IS_ARCH_AARCH32) {
193             architecture = "arm32v7";
194         }
195         return architecture;
196     }
197 
198     private static String computeOS() {
199         String os = EmbeddedUtil.OS_NAME;
200         if (EmbeddedUtil.IS_OS_LINUX) {
201             os = "linux";
202         } else if (EmbeddedUtil.IS_OS_MAC) {
203             os = "darwin";
204         } else if (EmbeddedUtil.IS_OS_WINDOWS) {
205             os = "windows";
206         }
207         return os;
208     }
209 }