From b8cb8fff2e49e937f5128dc856519568bb1699d8 Mon Sep 17 00:00:00 2001 From: Oliver Siegmar Date: Thu, 28 Mar 2024 17:17:27 +0100 Subject: [PATCH] refactor retry code; configure keystore without relying on System properties --- .../siegmar/logbackgelf/GelfTcpAppender.java | 43 +++--------- .../de/siegmar/logbackgelf/RetryUtil.java | 62 ++++++++++++++++++ .../logbackgelf/GelfTcpTlsAppenderTest.java | 52 +++++++++++++-- .../de/siegmar/logbackgelf/RetryUtilTest.java | 57 ++++++++++++++++ src/test/resources/mySrvKeystore | Bin 2243 -> 2726 bytes 5 files changed, 174 insertions(+), 40 deletions(-) create mode 100644 src/main/java/de/siegmar/logbackgelf/RetryUtil.java create mode 100644 src/test/java/de/siegmar/logbackgelf/RetryUtilTest.java diff --git a/src/main/java/de/siegmar/logbackgelf/GelfTcpAppender.java b/src/main/java/de/siegmar/logbackgelf/GelfTcpAppender.java index 9e7c990..37c756c 100644 --- a/src/main/java/de/siegmar/logbackgelf/GelfTcpAppender.java +++ b/src/main/java/de/siegmar/logbackgelf/GelfTcpAppender.java @@ -161,48 +161,25 @@ protected SocketFactory initSocketFactory() { return SocketFactory.getDefault(); } + @SuppressWarnings("checkstyle:IllegalCatch") @Override protected void appendMessage(final byte[] messageToSend) { - int openRetries = maxRetries; - do { - if (sendMessage(messageToSend)) { - // Message was sent successfully - we're done with it - break; - } - - if (retryDelay > 0 && openRetries > 0) { - try { - Thread.sleep(retryDelay); - } catch (final InterruptedException e) { - Thread.currentThread().interrupt(); - break; - } - } - } while (openRetries-- > 0 && isStarted()); + try { + RetryUtil.retry(() -> sendMessage(messageToSend), this::isStarted, maxRetries, retryDelay); + } catch (final Exception e) { + addError(String.format("Error sending message via tcp://%s:%s", + getGraylogHost(), getGraylogPort()), e); + } } /** * Send message to socket's output stream. * * @param messageToSend message to send. - * - * @return {@code true} if message was sent successfully, {@code false} otherwise. */ - @SuppressWarnings("checkstyle:illegalcatch") - private boolean sendMessage(final byte[] messageToSend) { - try { - connectionPool.execute(tcpConnection -> tcpConnection.write(messageToSend)); - } catch (final InterruptedException e) { - Thread.currentThread().interrupt(); - return false; - } catch (final Exception e) { - addError(String.format("Error sending message via tcp://%s:%s", - getGraylogHost(), getGraylogPort()), e); - - return false; - } - - return true; + private Void sendMessage(final byte[] messageToSend) throws Exception { + connectionPool.execute(tcpConnection -> tcpConnection.write(messageToSend)); + return null; } @Override diff --git a/src/main/java/de/siegmar/logbackgelf/RetryUtil.java b/src/main/java/de/siegmar/logbackgelf/RetryUtil.java new file mode 100644 index 0000000..a4062af --- /dev/null +++ b/src/main/java/de/siegmar/logbackgelf/RetryUtil.java @@ -0,0 +1,62 @@ +/* + * Logback GELF - zero dependencies Logback GELF appender library. + * Copyright (C) 2024 Oliver Siegmar + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package de.siegmar.logbackgelf; + +import java.util.concurrent.Callable; +import java.util.function.BooleanSupplier; + +final class RetryUtil { + + private RetryUtil() { + // Utility class + } + + @SuppressWarnings("checkstyle:IllegalCatch") + public static T retry(final Callable action, final BooleanSupplier retryCondition, final int maxRetries, + final long retryDelay) { + int retryCount = 0; + while (true) { + try { + return action.call(); + } catch (final Exception e) { + retryCount++; + if (retryCount > maxRetries || !retryCondition.getAsBoolean()) { + rethrow(e); + } + + try { + Thread.sleep(retryDelay); + } catch (final InterruptedException ie) { + Thread.currentThread().interrupt(); + rethrow(e); + } + } + } + } + + private static void rethrow(final Exception e) { + if (e instanceof RuntimeException) { + throw (RuntimeException) e; + } + + throw new IllegalStateException(e); + } + +} diff --git a/src/test/java/de/siegmar/logbackgelf/GelfTcpTlsAppenderTest.java b/src/test/java/de/siegmar/logbackgelf/GelfTcpTlsAppenderTest.java index 86a9953..36fac09 100644 --- a/src/test/java/de/siegmar/logbackgelf/GelfTcpTlsAppenderTest.java +++ b/src/test/java/de/siegmar/logbackgelf/GelfTcpTlsAppenderTest.java @@ -27,14 +27,25 @@ import java.io.IOException; import java.net.Socket; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; import javax.net.ssl.SSLServerSocket; import javax.net.ssl.SSLServerSocketFactory; +import javax.net.ssl.TrustManagerFactory; import org.junit.jupiter.api.Test; import org.slf4j.LoggerFactory; @@ -124,21 +135,48 @@ private static final class TlsServer implements Closeable { private final SSLServerSocket socket; private final Future receivedMessage; - TlsServer() throws IOException { + TlsServer() { socket = initSocket(); receivedMessage = Executors.newSingleThreadExecutor() .submit(this::receive); } - private SSLServerSocket initSocket() throws IOException { - System.setProperty("javax.net.ssl.keyStore", "src/test/resources/mySrvKeystore"); - System.setProperty("javax.net.ssl.keyStorePassword", "secret"); + private SSLServerSocket initSocket() { + final var socketFactory = getSSLServerSocketFactory(getFromPath()); + try { + return (SSLServerSocket) socketFactory.createServerSocket(0); + } catch (final IOException e) { + throw new IllegalStateException(e); + } + } + + public static SSLServerSocketFactory getSSLServerSocketFactory(final KeyStore trustKey) { + try { + final var kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(trustKey, "secret".toCharArray()); + + final var tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(trustKey); - final SSLServerSocketFactory socketFactory = - (SSLServerSocketFactory) SSLServerSocketFactory.getDefault(); + final var context = SSLContext.getInstance("TLS"); + context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); - return (SSLServerSocket) socketFactory.createServerSocket(0); + return context.getServerSocketFactory(); + } catch (final UnrecoverableKeyException | NoSuchAlgorithmException | KeyManagementException + | KeyStoreException e) { + throw new IllegalStateException(e); + } + } + + public static KeyStore getFromPath() { + try (var keyFile = Files.newInputStream(Path.of("src/test/resources/mySrvKeystore"))) { + final var keyStore = KeyStore.getInstance("PKCS12"); + keyStore.load(keyFile, "secret".toCharArray()); + return keyStore; + } catch (final IOException | CertificateException | KeyStoreException | NoSuchAlgorithmException e) { + throw new IllegalStateException(e); + } } int getPort() { diff --git a/src/test/java/de/siegmar/logbackgelf/RetryUtilTest.java b/src/test/java/de/siegmar/logbackgelf/RetryUtilTest.java new file mode 100644 index 0000000..73ac76b --- /dev/null +++ b/src/test/java/de/siegmar/logbackgelf/RetryUtilTest.java @@ -0,0 +1,57 @@ +/* + * Logback GELF - zero dependencies Logback GELF appender library. + * Copyright (C) 2024 Oliver Siegmar + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package de.siegmar.logbackgelf; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.jupiter.api.Test; + +class RetryUtilTest { + + private final AtomicInteger counter = new AtomicInteger(); + + @Test + void shouldRetryUntilMaxRetries() { + assertThatThrownBy(() -> RetryUtil.retry(() -> incCounter(counter), () -> true, 5, 1)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Retry"); + assertThat(counter).hasValue(6); + } + + @Test + void shouldRetryUntilMaxRetriesWhenConditionIsFalse() { + assertThatThrownBy(() -> RetryUtil.retry(() -> incCounter(counter), () -> false, 5, 1)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Retry"); + assertThat(counter).hasValue(1); + } + + private static int incCounter(final AtomicInteger counter) { + final int i = counter.incrementAndGet(); + if (i > 0) { + throw new IllegalStateException("Retry"); + } + return i; + } + +} diff --git a/src/test/resources/mySrvKeystore b/src/test/resources/mySrvKeystore index 3a03f6c716c5ddad1b08e160df0bed53e69fb6df..c8dd521e2ed8042e2a38aebaa2e0533f7f506944 100644 GIT binary patch literal 2726 zcma);X*d*&7RP5a1`{*35xIB)eUXcg8-S7m z^LZv%0(}pGfJz`isnF+2A0)!<1h#h5doM~^fykiYJ2_%FyNR`*8#zLFhqc zXU_fFRta&Nr)ro-;fX(57tOpX@>`it7|@Ahavvk%&mUB~ikLpt%+Kv136xk}+l^lk zmraL#ou5KIp0-;r8uoM1Zkjog-o2{6zTV;=K6Z~OfQ8nvOkWZki}~EB$&lO(jGbbg zYfrto%}kB!5N#GQF$?YQ81UH)i%AZSh?@n^)l$ss`^!6MU8Z4qDktb6)qFemqKgEy zaCG}mjNU|-te?t~ZBpB+&QQmrw@K{_DZxJoU%QP4U-K8?$ZR z(;0JOYiG;OSJj%T!-^_`#0&^+bCL%dc{h%@-~9X`H?}3^*gc{ÁJL4Pw{@?F~0 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 MC-?NkXh`ut0LP`^dH?_b