diff --git a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java index 69121cd6e..3f35529ec 100644 --- a/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java +++ b/src/main/java/org/cryptomator/launcher/AdminPropertiesSetter.java @@ -2,7 +2,6 @@ package org.cryptomator.launcher; import org.apache.commons.lang3.SystemUtils; import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -37,7 +36,7 @@ import java.util.Set; */ class AdminPropertiesSetter { - private static final Logger LOG = LoggerFactory.getLogger(AdminPropertiesSetter.class); + private static final Logger LOG = EventualLogger.getInstance(); private static final String LINUX_DIR = "/etc/cryptomator"; private static final String MAC_DIR = "/Library/Application Support/Cryptomator"; diff --git a/src/main/java/org/cryptomator/launcher/BufferedLog.java b/src/main/java/org/cryptomator/launcher/BufferedLog.java deleted file mode 100644 index 68eb75e26..000000000 --- a/src/main/java/org/cryptomator/launcher/BufferedLog.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.cryptomator.launcher; - -import org.slf4j.Logger; - -import java.util.ArrayList; -import java.util.List; - -class BufferedLog { - - private final static List logMessages = new ArrayList<>(); - - private BufferedLog() { - } - - record Entry(String className, String message, List messageInput) {} - - synchronized static void log(String className, String message, List messageInput) { - logMessages.add(new Entry(className, message, messageInput)); - } - - synchronized static void flushTo(Logger log) { - logMessages.forEach(e -> { - var message = "PRE LOG INIT Event in %s: %s".formatted(e.className, e.message); - log.info(message, e.messageInput.toArray()); - }); - logMessages.clear(); - } -} diff --git a/src/main/java/org/cryptomator/launcher/Cryptomator.java b/src/main/java/org/cryptomator/launcher/Cryptomator.java index 7bbcb8f05..70e34a2bd 100644 --- a/src/main/java/org/cryptomator/launcher/Cryptomator.java +++ b/src/main/java/org/cryptomator/launcher/Cryptomator.java @@ -65,7 +65,7 @@ public class Cryptomator { } public static void main(String[] args) { - BufferedLog.flushTo(LOG); + EventualLogger.getInstance().drainTo(LOG); var printVersion = Optional.ofNullable(args) // .stream() //Streams either one element (the args-array) or zero elements .flatMap(Arrays::stream) // diff --git a/src/main/java/org/cryptomator/launcher/EventualLogger.java b/src/main/java/org/cryptomator/launcher/EventualLogger.java new file mode 100644 index 000000000..0d3a0ffaa --- /dev/null +++ b/src/main/java/org/cryptomator/launcher/EventualLogger.java @@ -0,0 +1,115 @@ +package org.cryptomator.launcher; + +import org.slf4j.Logger; +import org.slf4j.Marker; +import org.slf4j.event.DefaultLoggingEvent; +import org.slf4j.event.Level; +import org.slf4j.event.LoggingEvent; +import org.slf4j.helpers.AbstractLogger; + +import java.util.ArrayDeque; +import java.util.Queue; + +class EventualLogger extends AbstractLogger { + + static EventualLogger getInstance() { + return Wrapped.INSTANCE.get(); + } + + + private final Queue bufferedEvents = new ArrayDeque<>(); + + private EventualLogger() { + } + + synchronized void drainTo(Logger gutter) { + for (var event : bufferedEvents) { + gutter.atLevel(event.getLevel()).log(event.getMessage(), event.getArgumentArray()); + } + bufferedEvents.clear(); + } + + @Override + protected synchronized void handleNormalizedLoggingCall(Level level, Marker marker, String messagePattern, Object[] arguments, Throwable throwable) { + var event = new DefaultLoggingEvent(level, this); + if (marker != null) { + event.addMarker(marker); + } + event.setMessage(messagePattern); + for (var arg : arguments) { + event.addArgument(arg); + } + bufferedEvents.add(event); + } + + //Unclear, unused and undocumented method of slf4j, see also https://github.com/qos-ch/slf4j/discussions/348 + @Override + protected String getFullyQualifiedCallerName() { + return getClass().getCanonicalName(); + } + + + @Override + public boolean isTraceEnabled() { + return true; + } + + @Override + public boolean isTraceEnabled(Marker marker) { + return true; + } + + @Override + public boolean isDebugEnabled() { + return true; + } + + @Override + public boolean isDebugEnabled(Marker marker) { + return true; + } + + @Override + public boolean isInfoEnabled() { + return true; + } + + @Override + public boolean isInfoEnabled(Marker marker) { + return true; + } + + @Override + public boolean isWarnEnabled() { + return true; + } + + @Override + public boolean isWarnEnabled(Marker marker) { + return true; + } + + @Override + public boolean isErrorEnabled() { + return true; + } + + @Override + public boolean isErrorEnabled(Marker marker) { + return true; + } + + private enum Wrapped { + INSTANCE; + + EventualLogger actualInstance; + + Wrapped() { + actualInstance = new EventualLogger(); + } + + public EventualLogger get() { + return actualInstance; + } + } +} diff --git a/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java b/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java index 31b6be658..bb2ae97eb 100644 --- a/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java +++ b/src/test/java/org/cryptomator/launcher/AdminPropertiesSetterTest.java @@ -1,98 +1,59 @@ package org.cryptomator.launcher; -import com.fasterxml.jackson.databind.json.JsonMapper; import org.hamcrest.MatcherAssert; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import org.mockito.Answers; -import org.mockito.Mockito; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.List; -import java.util.Map; import static org.hamcrest.Matchers.anEmptyMap; import static org.hamcrest.Matchers.hasEntry; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; public class AdminPropertiesSetterTest { - private static final Map NO_STRING_CONFIG = Map.of("list", List.of("a", "b", "c"), // - "map", Map.of("a", 1, "b", 2)); - - private static final Map CONFIG = Map.of("kack", "dudel", // - "list", List.of("a", "b", "c"), // - "map", Map.of("a", 1, "b", 2)); + private static final String PROPS = """ + fruit=banana + vegetable:kärrot + method=scan寧"""; @Test - @DisplayName("Loading valid JSON") - void loadValidJson(@TempDir Path path) throws IOException { - try (var adminPropSetterMock = Mockito.mockStatic(AdminPropertiesSetter.class)) { - adminPropSetterMock.when(() -> AdminPropertiesSetter.log(anyString(), any())).thenAnswer(Answers.RETURNS_DEFAULTS); - adminPropSetterMock.when(() -> AdminPropertiesSetter.loadAdminProperties(any())).thenCallRealMethod(); - var configPath = path.resolve("config.json"); - setupValidJson(configPath); + @DisplayName("UTF-8 is supported") + void loadUTF8Properties(@TempDir Path path) throws IOException { + var config = path.resolve("config.properties"); + setupValidProperties(config); - var result = AdminPropertiesSetter.loadAdminProperties(configPath); - Assertions.assertAll(CONFIG.entrySet().stream().map((e) -> // - () -> MatcherAssert.assertThat(result, hasEntry(e.getKey(), e.getValue())))); - } + var properties = AdminPropertiesSetter.loadAdminProperties(config); + Assertions.assertAll(List.of( // + () -> MatcherAssert.assertThat(properties, hasEntry("fruit", "banana")), // + () -> MatcherAssert.assertThat(properties, hasEntry("vegetable", "kärrot")), // + () -> MatcherAssert.assertThat(properties, hasEntry("method", "scan寧")))); } @Test @DisplayName("Loading not existing file") void loadNotExistingFile(@TempDir Path path) { - try (var adminPropSetterMock = Mockito.mockStatic(AdminPropertiesSetter.class)) { - adminPropSetterMock.when(() -> AdminPropertiesSetter.log(anyString(), any())).thenAnswer(Answers.RETURNS_DEFAULTS); - adminPropSetterMock.when(() -> AdminPropertiesSetter.loadAdminProperties(any())).thenCallRealMethod(); - var configPath = path.resolve("config.json"); - - var result = AdminPropertiesSetter.loadAdminProperties(configPath); - MatcherAssert.assertThat(result, anEmptyMap()); - adminPropSetterMock.verify(() -> AdminPropertiesSetter.log(anyString(), any())); - } + var config = path.resolve("config.properties"); + var properties = AdminPropertiesSetter.loadAdminProperties(config); + MatcherAssert.assertThat(properties, anEmptyMap()); } @Test - @DisplayName("Loading empty file") + @DisplayName("Loading invalid properties file") void loadEmptyFile(@TempDir Path path) throws IOException { - try (var adminPropSetterMock = Mockito.mockStatic(AdminPropertiesSetter.class)) { - adminPropSetterMock.when(() -> AdminPropertiesSetter.log(anyString(), any())).thenAnswer(Answers.RETURNS_DEFAULTS); - adminPropSetterMock.when(() -> AdminPropertiesSetter.loadAdminProperties(any())).thenCallRealMethod(); - var configPath = path.resolve("config.json"); - Files.createFile(configPath); - - var result = AdminPropertiesSetter.loadAdminProperties(configPath); - MatcherAssert.assertThat(result, anEmptyMap()); - adminPropSetterMock.verify(() -> AdminPropertiesSetter.log(anyString(), any())); - } } - void setupValidJson(Path p) throws IOException { - var json = JsonMapper.builder().build(); + void setupValidProperties(Path p) throws IOException { try (var out = Files.newOutputStream(p, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE)) { - json.writerWithDefaultPrettyPrinter().writeValue(out, CONFIG); + var bytes = PROPS.getBytes(StandardCharsets.UTF_8); + out.write(bytes); } } - @Test - @DisplayName("Keys with non-String values are ignored") - void ignoreValues(@TempDir Path path) { - try (var adminPropSetterMock = Mockito.mockStatic(AdminPropertiesSetter.class)) { - adminPropSetterMock.when(() -> AdminPropertiesSetter.log(anyString(), any())).thenAnswer(Answers.RETURNS_DEFAULTS); - adminPropSetterMock.when(() -> AdminPropertiesSetter.loadAdminProperties(any())).thenReturn(NO_STRING_CONFIG); - adminPropSetterMock.when(AdminPropertiesSetter::adjustSystemProperties).thenCallRealMethod(); - - AdminPropertiesSetter.adjustSystemProperties(); - adminPropSetterMock.verify(() -> AdminPropertiesSetter.log(anyString(), any()), Mockito.times(NO_STRING_CONFIG.size())); - } - } - - }