Skip to content

Commit

Permalink
refactor retry code; configure keystore without relying on System pro…
Browse files Browse the repository at this point in the history
…perties
  • Loading branch information
osiegmar committed Mar 28, 2024
1 parent d2c4912 commit b8cb8ff
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 40 deletions.
43 changes: 10 additions & 33 deletions src/main/java/de/siegmar/logbackgelf/GelfTcpAppender.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
62 changes: 62 additions & 0 deletions src/main/java/de/siegmar/logbackgelf/RetryUtil.java
Original file line number Diff line number Diff line change
@@ -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> T retry(final Callable<T> 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);
}

}
52 changes: 45 additions & 7 deletions src/test/java/de/siegmar/logbackgelf/GelfTcpTlsAppenderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -124,21 +135,48 @@ private static final class TlsServer implements Closeable {
private final SSLServerSocket socket;
private final Future<byte[]> 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() {
Expand Down
57 changes: 57 additions & 0 deletions src/test/java/de/siegmar/logbackgelf/RetryUtilTest.java
Original file line number Diff line number Diff line change
@@ -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;
}

}
Binary file modified src/test/resources/mySrvKeystore
Binary file not shown.

0 comments on commit b8cb8ff

Please sign in to comment.