Explorar el Código

Refactor properties preprocessing:
* decorate Properties class
* set the system properties to decorator
* for logging setup, skip enviroment and access props over decorator

Armin Schrenk hace 1 año
padre
commit
01da51e6e7

+ 255 - 0
src/main/java/org/cryptomator/common/LazyProcessedProperties.java

@@ -0,0 +1,255 @@
+package org.cryptomator.common;
+
+import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.nio.charset.Charset;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.InvalidPropertiesFormatException;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.regex.Pattern;
+
+public class LazyProcessedProperties extends Properties {
+
+	private static final Logger LOG = LoggerFactory.getLogger(LazyProcessedProperties.class);
+
+	//Template and env _need_ to be instance variables, otherwise they might not be initialized at access time
+	private final Pattern template = Pattern.compile("@\\{(\\w+)}");
+	private final Map<String, String> env = System.getenv();
+	private final Properties delegate;
+
+	public LazyProcessedProperties(Properties props) {
+		this.delegate = props;
+	}
+
+	@Override
+	public String getProperty(String key) {
+		var value = delegate.getProperty(key);
+		if (key.startsWith("cryptomator.") && value != null) {
+			return process(value);
+		} else {
+			return value;
+		}
+	}
+
+	@Override
+	public String getProperty(String key, String defaultValue) {
+		var value = delegate.getProperty(key, defaultValue);
+		if (key.startsWith("cryptomator.") && value != null) {
+			return process(value);
+		} else {
+			return value;
+		}
+	}
+
+	String process(String value) {
+		return template.matcher(value).replaceAll(match -> //
+				switch (match.group(1)) {
+					case "appdir" -> resolveFrom("APPDIR", Source.ENV);
+					case "appdata" -> resolveFrom("APPDATA", Source.ENV);
+					case "localappdata" -> resolveFrom("LOCALAPPDATA", Source.ENV);
+					case "userhome" -> resolveFrom("user.home", Source.PROPS);
+					default -> {
+						LOG.warn("Unknown variable @{{}} in property value {}.", match.group(), value);
+						yield match.group();
+					}
+				});
+	}
+
+	private String resolveFrom(String key, Source src) {
+		var val = switch (src) {
+			case ENV -> env.get(key);
+			case PROPS -> delegate.getProperty(key);
+		};
+		if (val == null) {
+			LOG.warn("Variable {} used for substitution not found in {}. Replaced with empty string.", key, src);
+			return "";
+		} else {
+			return val.replace("\\", "\\\\");
+		}
+	}
+
+	private enum Source {
+		ENV,
+		PROPS;
+	}
+
+	@Override
+	public Object setProperty(String key, String value) {
+		return delegate.setProperty(key, value);
+	}
+
+	//auto generated
+	@Override
+	public void load(Reader reader) throws IOException {delegate.load(reader);}
+
+	@Override
+	public void load(InputStream inStream) throws IOException {delegate.load(inStream);}
+
+	@Override
+	@Deprecated
+	public void save(OutputStream out, String comments) {delegate.save(out, comments);}
+
+	@Override
+	public void store(Writer writer, String comments) throws IOException {delegate.store(writer, comments);}
+
+	@Override
+	public void store(OutputStream out, @Nullable String comments) throws IOException {delegate.store(out, comments);}
+
+	@Override
+	public void loadFromXML(InputStream in) throws IOException, InvalidPropertiesFormatException {delegate.loadFromXML(in);}
+
+	@Override
+	public void storeToXML(OutputStream os, String comment) throws IOException {delegate.storeToXML(os, comment);}
+
+	@Override
+	public void storeToXML(OutputStream os, String comment, String encoding) throws IOException {delegate.storeToXML(os, comment, encoding);}
+
+	@Override
+	public void storeToXML(OutputStream os, String comment, Charset charset) throws IOException {delegate.storeToXML(os, comment, charset);}
+
+	@Override
+	public Enumeration<?> propertyNames() {return delegate.propertyNames();}
+
+	@Override
+	public Set<String> stringPropertyNames() {return delegate.stringPropertyNames();}
+
+	@Override
+	public void list(PrintStream out) {delegate.list(out);}
+
+	@Override
+	public void list(PrintWriter out) {delegate.list(out);}
+
+	@Override
+	public int size() {return delegate.size();}
+
+	@Override
+	public boolean isEmpty() {return delegate.isEmpty();}
+
+	@Override
+	public Enumeration<Object> keys() {return delegate.keys();}
+
+	@Override
+	public Enumeration<Object> elements() {return delegate.elements();}
+
+	@Override
+	public boolean contains(Object value) {return delegate.contains(value);}
+
+	@Override
+	public boolean containsValue(Object value) {return delegate.containsValue(value);}
+
+	@Override
+	public boolean containsKey(Object key) {return delegate.containsKey(key);}
+
+	@Override
+	public Object get(Object key) {return delegate.get(key);}
+
+	@Override
+	public Object put(Object key, Object value) {return delegate.put(key, value);}
+
+	@Override
+	public Object remove(Object key) {return delegate.remove(key);}
+
+	@Override
+	public void putAll(Map<?, ?> t) {delegate.putAll(t);}
+
+	@Override
+	public void clear() {delegate.clear();}
+
+	@Override
+	public String toString() {return delegate.toString();}
+
+	@Override
+	public Set<Object> keySet() {return delegate.keySet();}
+
+	@Override
+	public Collection<Object> values() {return delegate.values();}
+
+	@Override
+	public Set<Map.Entry<Object, Object>> entrySet() {return delegate.entrySet();}
+
+	@Override
+	public boolean equals(Object o) {return delegate.equals(o);}
+
+	@Override
+	public int hashCode() {return delegate.hashCode();}
+
+	@Override
+	public Object getOrDefault(Object key, Object defaultValue) {return delegate.getOrDefault(key, defaultValue);}
+
+	@Override
+	public void forEach(BiConsumer<? super Object, ? super Object> action) {delegate.forEach(action);}
+
+	@Override
+	public void replaceAll(BiFunction<? super Object, ? super Object, ?> function) {delegate.replaceAll(function);}
+
+	@Override
+	public Object putIfAbsent(Object key, Object value) {return delegate.putIfAbsent(key, value);}
+
+	@Override
+	public boolean remove(Object key, Object value) {return delegate.remove(key, value);}
+
+	@Override
+	public boolean replace(Object key, Object oldValue, Object newValue) {return delegate.replace(key, oldValue, newValue);}
+
+	@Override
+	public Object replace(Object key, Object value) {return delegate.replace(key, value);}
+
+	@Override
+	public Object computeIfAbsent(Object key, Function<? super Object, ?> mappingFunction) {return delegate.computeIfAbsent(key, mappingFunction);}
+
+	@Override
+	public Object computeIfPresent(Object key, BiFunction<? super Object, ? super Object, ?> remappingFunction) {return delegate.computeIfPresent(key, remappingFunction);}
+
+	@Override
+	public Object compute(Object key, BiFunction<? super Object, ? super Object, ?> remappingFunction) {return delegate.compute(key, remappingFunction);}
+
+	@Override
+	public Object merge(Object key, Object value, BiFunction<? super Object, ? super Object, ?> remappingFunction) {return delegate.merge(key, value, remappingFunction);}
+
+	@Override
+	public Object clone() {return delegate.clone();}
+
+	public static <K, V> Map<K, V> of() {return Map.of();}
+
+	public static <K, V> Map<K, V> of(K k1, V v1) {return Map.of(k1, v1);}
+
+	public static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2) {return Map.of(k1, v1, k2, v2);}
+
+	public static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3) {return Map.of(k1, v1, k2, v2, k3, v3);}
+
+	public static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) {return Map.of(k1, v1, k2, v2, k3, v3, k4, v4);}
+
+	public static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) {return Map.of(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5);}
+
+	public static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6) {return Map.of(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5, k6, v6);}
+
+	public static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7) {return Map.of(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5, k6, v6, k7, v7);}
+
+	public static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7, K k8, V v8) {return Map.of(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5, k6, v6, k7, v7, k8, v8);}
+
+	public static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7, K k8, V v8, K k9, V v9) {return Map.of(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5, k6, v6, k7, v7, k8, v8, k9, v9);}
+
+	public static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7, K k8, V v8, K k9, V v9, K k10, V v10) {return Map.of(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5, k6, v6, k7, v7, k8, v8, k9, v9, k10, v10);}
+
+	@SafeVarargs
+	public static <K, V> Map<K, V> ofEntries(Map.Entry<? extends K, ? extends V>... entries) {return Map.ofEntries(entries);}
+
+	public static <K, V> Map.Entry<K, V> entry(K k, V v) {return Map.entry(k, v);}
+
+	public static <K, V> Map<K, V> copyOf(Map<? extends K, ? extends V> map) {return Map.copyOf(map);}
+}

+ 0 - 69
src/main/java/org/cryptomator/common/PropertiesPreprocessor.java

@@ -1,69 +0,0 @@
-package org.cryptomator.common;
-
-import org.jetbrains.annotations.VisibleForTesting;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.Map;
-import java.util.regex.Pattern;
-
-public class PropertiesPreprocessor {
-
-	private static final Logger LOG = LoggerFactory.getLogger(PropertiesPreprocessor.class);
-	private static final Pattern TEMPLATE = Pattern.compile("@\\{(\\w+)}");
-	private static final LoggingEnvironment ENV = new LoggingEnvironment(System.getenv(), LOG);
-
-	public static void run() {
-		var properties = System.getProperties();
-		properties.stringPropertyNames().stream() //
-				.filter(s -> s.startsWith("cryptomator.")) //
-				.forEach(key -> {
-					var value = properties.getProperty(key);
-					var newValue = process(value);
-					if(! value.equals(newValue)) {
-						LOG.info("Changed property {} from {} to {}.", key, value, newValue);
-					}
-					properties.setProperty(key, newValue);
-				});
-		LOG.info("Preprocessed cryptomator properties.");
-	}
-
-	@VisibleForTesting
-	static String process(String value) {
-		return TEMPLATE.matcher(value).replaceAll(match -> //
-				switch (match.group(1)) {
-					case "appdir" -> ENV.get("APPDIR");
-					case "appdata" -> ENV.get("APPDATA");
-					case "localappdata" -> ENV.get("LOCALAPPDATA");
-					case "userhome" -> System.getProperty("user.home");
-					default -> {
-						LOG.warn("Found unknown variable @{{}} in property value {}.", match.group(), value);
-						yield match.group();
-					}
-				});
-	}
-
-	private static class LoggingEnvironment {
-
-		private final Map<String, String> env;
-		private final Logger logger;
-
-		LoggingEnvironment(Map<String, String> env, Logger logger) {
-			this.env = env;
-			this.logger = logger;
-		}
-
-		String get(String key) {
-			var val = env.get(key);
-			if (val == null) {
-				logger.warn("Variable {} used for substitution not found in environment", key);
-				return "";
-			} else {
-				return val;
-			}
-		}
-
-	}
-
-
-}

+ 11 - 4
src/main/java/org/cryptomator/launcher/Cryptomator.java

@@ -9,7 +9,7 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder;
 import dagger.Lazy;
 import org.apache.commons.lang3.SystemUtils;
 import org.cryptomator.common.Environment;
-import org.cryptomator.common.PropertiesPreprocessor;
+import org.cryptomator.common.LazyProcessedProperties;
 import org.cryptomator.common.ShutdownHook;
 import org.cryptomator.ipc.IpcCommunicator;
 import org.cryptomator.logging.DebugMode;
@@ -32,8 +32,16 @@ public class Cryptomator {
 	private static final long STARTUP_TIME = System.currentTimeMillis();
 	// DaggerCryptomatorComponent gets generated by Dagger.
 	// Run Maven and include target/generated-sources/annotations in your IDE.
-	private static final CryptomatorComponent CRYPTOMATOR_COMPONENT = DaggerCryptomatorComponent.factory().create(STARTUP_TIME);
-	private static final Logger LOG = LoggerFactory.getLogger(Cryptomator.class);
+
+	static {
+		var lazyProcessedProps = new LazyProcessedProperties(System.getProperties());
+		System.setProperties(lazyProcessedProps);
+		CRYPTOMATOR_COMPONENT = DaggerCryptomatorComponent.factory().create(STARTUP_TIME);
+		LOG = LoggerFactory.getLogger(Cryptomator.class);
+	}
+
+	private static final CryptomatorComponent CRYPTOMATOR_COMPONENT;
+	private static final Logger LOG;
 
 	private final DebugMode debugMode;
 	private final SupportedLanguages supportedLanguages;
@@ -64,7 +72,6 @@ public class Cryptomator {
 			System.out.printf("Cryptomator version %s (build %s)%n", appVer, buildNumber);
 			return;
 		}
-		PropertiesPreprocessor.run();
 		int exitCode = CRYPTOMATOR_COMPONENT.application().run(args);
 		LOG.info("Exit {}", exitCode);
 		System.exit(exitCode); // end remaining non-daemon threads.

+ 7 - 3
src/main/java/org/cryptomator/logging/LogbackConfigurator.java

@@ -14,10 +14,12 @@ import ch.qos.logback.core.rolling.FixedWindowRollingPolicy;
 import ch.qos.logback.core.rolling.RollingFileAppender;
 import ch.qos.logback.core.spi.ContextAwareBase;
 import ch.qos.logback.core.util.FileSize;
-import org.cryptomator.common.Environment;
+import org.cryptomator.common.LazyProcessedProperties;
 
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Map;
+import java.util.Optional;
 
 public class LogbackConfigurator extends ContextAwareBase implements Configurator {
 
@@ -56,8 +58,10 @@ public class LogbackConfigurator extends ContextAwareBase implements Configurato
 
 	@Override
 	public ExecutionStatus configure(LoggerContext context) {
-		var useCustomCfg = Environment.getInstance().useCustomLogbackConfig();
-		var logDir = Environment.getInstance().getLogDir().orElse(null);
+		//we need to preprocess those, because every other class has a dependency to logging, none are initialized yet
+		var processedProps = new LazyProcessedProperties(System.getProperties());
+		var useCustomCfg = Optional.ofNullable(processedProps.getProperty("logback.configurationFile")).map(s -> Files.exists(Path.of(s))).orElse(false);
+		var logDir = Optional.ofNullable(processedProps.getProperty("cryptomator.logDir")).map(Path::of).orElse(null);
 
 		if (useCustomCfg) {
 			addInfo("Using external logback configuration file.");

+ 4 - 4
src/test/java/org/cryptomator/common/PropertiesPreprocessorTest.java

@@ -1,12 +1,11 @@
 package org.cryptomator.common;
 
 import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.CsvSource;
-import org.mockito.Mockito;
 
-public class PropertiesPreprocessorTest {
+public class LazyProcessedPropertiesTest {
 
 	@ParameterizedTest
 	@CsvSource(value = """
@@ -17,7 +16,8 @@ public class PropertiesPreprocessorTest {
 			Longer @{appdir} text with @{appdir}., Longer  text with .
 			""")
 	public void test(String propertyValue, String expected) {
-		var result = PropertiesPreprocessor.process(propertyValue);
+		LazyProcessedProperties inTest = new LazyProcessedProperties(System.getProperties());
+		var result = inTest.process(propertyValue);
 		Assertions.assertEquals(result, expected);
 	}