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