001/* 002 * Licensed under the Apache License, Version 2.0 (the "License"); 003 * you may not use this file except in compliance with the License. 004 * You may obtain a copy of the License at 005 * 006 * http://www.apache.org/licenses/LICENSE-2.0 007 * 008 * Unless required by applicable law or agreed to in writing, software 009 * distributed under the License is distributed on an "AS IS" BASIS, 010 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 011 * See the License for the specific language governing permissions and 012 * limitations under the License. 013 */ 014 015package de.softwareforge.testing.postgres.embedded; 016 017import static com.google.common.base.Preconditions.checkNotNull; 018import static com.google.common.base.Preconditions.checkState; 019import static java.lang.String.format; 020 021import de.softwareforge.testing.maven.MavenArtifactLoader; 022 023import java.io.File; 024import java.io.FilterInputStream; 025import java.io.IOException; 026import java.io.InputStream; 027import java.io.UncheckedIOException; 028import java.nio.charset.StandardCharsets; 029import java.util.Objects; 030import java.util.function.Supplier; 031import java.util.jar.JarEntry; 032import java.util.jar.JarFile; 033 034import com.google.common.base.Suppliers; 035import com.google.common.hash.HashCode; 036import com.google.common.hash.Hashing; 037import com.google.common.io.BaseEncoding; 038import org.slf4j.Logger; 039import org.slf4j.LoggerFactory; 040 041/** 042 * Resolves PostgreSQL archives from the Maven repository. Looks for the zonky.io artifacts located at 043 * <code>io.zonky.test.postgres:embedded-postgres-binaries-<os>-<arch></code>. 044 * <p> 045 * See <a href="https://github.com/zonkyio/embedded-postgres-binaries">The Zonky IO github page</a> for more details. 046 * 047 * @since 3.0 048 */ 049public final class ZonkyIOPostgresLocator implements NativeBinaryLocator { 050 051 private static final String ZONKY_GROUP_ID = "io.zonky.test.postgres"; 052 private static final String ZONKY_ARTIFACT_ID_TEMPLATE = "embedded-postgres-binaries-%s-%s"; 053 054 public static final Logger LOG = LoggerFactory.getLogger(ZonkyIOPostgresLocator.class); 055 056 private static final boolean PREFER_NATIVE = Boolean.getBoolean("pg-embedded.prefer-native"); 057 058 private final String architecture; 059 private final String os; 060 private final String serverVersion; 061 062 private final MavenArtifactLoader artifactLoader = new MavenArtifactLoader(); 063 064 private final Supplier<File> fileSupplier = Suppliers.memoize(this::loadArtifact); 065 066 ZonkyIOPostgresLocator(String serverVersion) { 067 this.serverVersion = checkNotNull(serverVersion, "serverVersion is null"); 068 069 this.os = computeOS(); 070 this.architecture = computeTarXzArchitectureName(); 071 LOG.debug(format("Detected a %s %s system, using PostgreSQL version %s/%s", EmbeddedUtil.OS_ARCH, os, serverVersion, architecture)); 072 } 073 074 @Override 075 public InputStream getInputStream() throws IOException { 076 try { 077 File artifactFile = fileSupplier.get(); 078 return createJarStream(artifactFile); 079 } catch (UncheckedIOException e) { 080 throw e.getCause(); 081 } 082 } 083 084 @Override 085 public String getIdentifier() throws IOException { 086 // the optimized identifier computation saves ~ 1% CPU according to the profiler 087 try { 088 File artifactFile = fileSupplier.get(); 089 HashCode hashCode = Hashing.murmur3_128().hashString(artifactFile.getAbsolutePath(), StandardCharsets.UTF_8); 090 return INSTALL_DIRECTORY_PREFIX + BaseEncoding.base16().encode(hashCode.asBytes()); 091 } catch (UncheckedIOException e) { 092 throw e.getCause(); 093 } 094 } 095 096 private File loadArtifact() { 097 try { 098 String artifactId = format(ZONKY_ARTIFACT_ID_TEMPLATE, this.os, computeJarArchitectureName()); 099 100 // alpine hack 101 if (EmbeddedUtil.IS_ALPINE_LINUX) { 102 artifactId += "-alpine"; 103 } 104 105 String version = artifactLoader.findLatestVersion(ZONKY_GROUP_ID, artifactId, serverVersion); 106 File file = artifactLoader.getArtifactFile(ZONKY_GROUP_ID, artifactId, version); 107 checkState(file != null && file.exists(), "Could not locate artifact file for %s:%s", artifactId, version); 108 LOG.info(format("Using PostgreSQL version %s (%s)", version, architecture)); 109 return file; 110 } catch (IOException e) { 111 throw new UncheckedIOException(e); 112 } 113 } 114 115 private InputStream createJarStream(File file) { 116 try { 117 JarFile jar = new JarFile(file); 118 String entryName = format("postgres-%s-%s", computeOS(), computeTarXzArchitectureName()); 119 120 // alpine hack 121 if (EmbeddedUtil.IS_ALPINE_LINUX) { 122 entryName += "-alpine_linux"; 123 } 124 125 JarEntry jarEntry = jar.getJarEntry(entryName + ".txz"); 126 checkState(jarEntry != null, "Could not locate %s in the jar file (%s)", entryName, file.getAbsoluteFile()); 127 128 // When the input stream gets closed, close the jar file as well. 129 return new FilterInputStream(jar.getInputStream(jarEntry)) { 130 @Override 131 public void close() throws IOException { 132 try { 133 super.close(); 134 } finally { 135 jar.close(); 136 } 137 } 138 }; 139 } catch (IOException e) { 140 throw new UncheckedIOException(e); 141 } 142 } 143 144 @Override 145 public String toString() { 146 return format("ZonkyIO Stream locator for PostgreSQL (machine: %s os: %s, arch: %s, version: %s)", EmbeddedUtil.OS_ARCH, os, architecture, serverVersion); 147 } 148 149 @Override 150 public boolean equals(Object o) { 151 if (this == o) { 152 return true; 153 } 154 if (o == null || getClass() != o.getClass()) { 155 return false; 156 } 157 ZonkyIOPostgresLocator that = (ZonkyIOPostgresLocator) o; 158 return architecture.equals(that.architecture) && os.equals(that.os) && serverVersion.equals(that.serverVersion); 159 } 160 161 @Override 162 public int hashCode() { 163 return Objects.hash(architecture, os, serverVersion); 164 } 165 166 private static String computeTarXzArchitectureName() { 167 String architecture = EmbeddedUtil.OS_ARCH; 168 if (EmbeddedUtil.IS_ARCH_X86_64) { 169 architecture = "x86_64"; // Zonky uses x86_64 170 } else if (EmbeddedUtil.IS_ARCH_AARCH64) { 171 if (!PREFER_NATIVE && EmbeddedUtil.IS_OS_MAC) { 172 // Mac binaries are fat binaries stored as x86_64 173 architecture = "x86_64"; 174 } else { 175 architecture = "arm_64"; 176 } 177 } else if (EmbeddedUtil.IS_ARCH_AARCH32) { 178 architecture = "arm_32"; 179 } 180 return architecture; 181 } 182 183 private static String computeJarArchitectureName() { 184 String architecture = EmbeddedUtil.OS_ARCH; 185 if (EmbeddedUtil.IS_ARCH_X86_64) { 186 architecture = "amd64"; // Zonky uses amd64 for the jar name 187 } else if (EmbeddedUtil.IS_ARCH_AARCH64) { 188 if (!PREFER_NATIVE && EmbeddedUtil.IS_OS_MAC) { 189 // Mac binaries are fat binaries stored as amd64 190 architecture = "amd64"; 191 } else { 192 architecture = "arm64v8"; 193 } 194 } else if (EmbeddedUtil.IS_ARCH_AARCH32) { 195 architecture = "arm32v7"; 196 } 197 return architecture; 198 } 199 200 private static String computeOS() { 201 String os = EmbeddedUtil.OS_NAME; 202 if (EmbeddedUtil.IS_OS_LINUX) { 203 os = "linux"; 204 } else if (EmbeddedUtil.IS_OS_MAC) { 205 os = "darwin"; 206 } else if (EmbeddedUtil.IS_OS_WINDOWS) { 207 os = "windows"; 208 } 209 return os; 210 } 211}