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.checkState; 18 19 import jakarta.annotation.Nonnull; 20 import jakarta.annotation.Nullable; 21 import java.io.IOException; 22 import java.io.InputStream; 23 24 import com.google.common.hash.Hashing; 25 import com.google.common.hash.HashingInputStream; 26 import com.google.common.io.BaseEncoding; 27 import com.google.common.io.ByteStreams; 28 29 /** 30 * Locates a native binary on the Filesystem. If necessary, it should download the binary first from the network. 31 * <p> 32 * Implementations of this class <b>must</b> implement {@link Object#hashCode()} and {@link Object#equals(Object)} and <i>should</i> implement 33 * {@link Object#toString()}. 34 * 35 * @since 3.0 36 */ 37 @SuppressWarnings("PMD.ImplicitFunctionalInterface") 38 public interface NativeBinaryLocator { 39 40 String INSTALL_DIRECTORY_PREFIX = "bin-"; 41 42 /** 43 * Returns an input stream from which the contents of the binary archive can be read. 44 * 45 * @return An input stream. May return null. 46 * @throws IOException If the archive could not be located or read. 47 */ 48 @Nullable 49 InputStream getInputStream() throws IOException; 50 51 /** 52 * Returns a string identifier that represents the archive returned. This identifier should be stable (same value for the same archive), even across 53 * multiple JVM invocations. The value must only contain characters that can be used as a legal file name. 54 * <p> 55 * The default implementation needs to read the full archive contents and is relatively slow. Implementations of this interface can override this method to 56 * provide a faster way to create a stable identifier based on the specific implementation. 57 * <p> 58 * The default implementation hashes the archive contents and uses it to return a stable file name. 59 * 60 * @return A stable indentifier that can be used as a file name. 61 * @throws IOException If the stream could not be read. 62 */ 63 @Nonnull 64 @SuppressWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE") // false positive 65 default String getIdentifier() throws IOException { 66 try (InputStream installationArchive = getInputStream()) { 67 checkState(installationArchive != null, "Locator '%s' did not find a suitable archive to unpack!", toString()); 68 try (HashingInputStream hashStream = new HashingInputStream(Hashing.murmur3_128(), installationArchive)) { 69 ByteStreams.exhaust(hashStream); 70 return INSTALL_DIRECTORY_PREFIX + BaseEncoding.base16().encode(hashStream.hash().asBytes()); 71 } 72 } 73 } 74 }