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 */ 047public final class ZonkyIOPostgresLocator implements NativeBinaryLocator { 048 049 private static final String ZONKY_GROUP_ID = "io.zonky.test.postgres"; 050 private static final String ZONKY_ARTIFACT_ID_TEMPLATE = "embedded-postgres-binaries-%s-%s"; 051 052 public static final Logger LOG = LoggerFactory.getLogger(ZonkyIOPostgresLocator.class); 053 054 private static final boolean PREFER_NATIVE = Boolean.getBoolean("pg-embedded.prefer-native"); 055 056 private final String architecture; 057 private final String os; 058 private final String serverVersion; 059 060 private final MavenArtifactLoader artifactLoader = new MavenArtifactLoader(); 061 062 private final Supplier<File> fileSupplier = Suppliers.memoize(this::loadArtifact); 063 064 ZonkyIOPostgresLocator(String serverVersion) { 065 this.serverVersion = checkNotNull(serverVersion, "serverVersion is null"); 066 067 this.os = computeOS(); 068 this.architecture = computeTarXzArchitectureName(); 069 LOG.debug(format("Detected a %s %s system, using PostgreSQL version %s", architecture, os, serverVersion)); 070 } 071 072 @Override 073 public InputStream getInputStream() throws IOException { 074 try { 075 File artifactFile = fileSupplier.get(); 076 return createJarStream(artifactFile); 077 } catch (UncheckedIOException e) { 078 throw e.getCause(); 079 } 080 } 081 082 @Override 083 public String getIdentifier() throws IOException { 084 // the optimized identifier computation saves ~ 1% CPU according to the profiler 085 try { 086 File artifactFile = fileSupplier.get(); 087 HashCode hashCode = Hashing.murmur3_128().hashString(artifactFile.getAbsolutePath(), StandardCharsets.UTF_8); 088 return INSTALL_DIRECTORY_PREFIX + BaseEncoding.base16().encode(hashCode.asBytes()); 089 } catch (UncheckedIOException e) { 090 throw e.getCause(); 091 } 092 } 093 094 private File loadArtifact() { 095 try { 096 String artifactId = format(ZONKY_ARTIFACT_ID_TEMPLATE, this.os, computeJarArchitectureName()); 097 098 // alpine hack 099 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 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}