/*
* Copyright (C) 2015 SoftIndex LLC.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.datakernel.config;
import com.google.inject.*;
import com.google.inject.util.Providers;
import io.datakernel.launcher.Launcher;
import io.datakernel.service.BlockingService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
/**
* Represents an easy-to-use properties module, capable of providing a
* {@link Config} instance with properties using various parameter types
* (e.g. file, filepath or just {@code Properties} object).
* <p>
* The module is able to unite given configs and save an optimized result into a
* single file. The usage example of {@code PropertiesConfigModule}:
* <pre><code>
* PropertiesConfigModule.{@link #ofFile(String) ofFile("main.properties)}
* .{@link #addFile(String) addFile("extra.properties")}
* .{@link #addOptionalFile(String) addOptionalFile(optional.properties")}
* .{@link #saveEffectiveConfigTo(File)} saveEffectiveConfigTo("all.properties")};
* </code></pre>
* <p>
* An ease of use of this module are noticeable, for example, when a
* {@link Config} is required for instantiating a complex application.
* {@code PropertiesConfigModule} simplifies configuration of an application
* when used with a {@link Launcher launcher}. Just pass a
* {@code PropertiesConfigModule} containing desired configs to the launcher.
* <p>
* A usage example of {@code PropertiesConfigModule} along with
* {@code Launcher} listed in {@link Launcher} is worth seeing.
*
* @see Config
* @see Launcher
* @see Launcher#Launcher(Stage, Module...)
*/
public final class PropertiesConfigModule extends AbstractModule {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final List<Provider<Properties>> properties = new ArrayList<>();
private File saveFile;
private Config root;
private PropertiesConfigModule() {
}
public static PropertiesConfigModule create() {
return new PropertiesConfigModule();
}
public static PropertiesConfigModule ofFile(File file) {
return PropertiesConfigModule.create().addFile(file);
}
/**
* Creates a module with configs contained in given file
*
* @param file a path to a file with configs
* @return a new module with configs, contained in a file
* @throws IllegalArgumentException if an IOException occurred
*/
public static PropertiesConfigModule ofFile(String file) {
return PropertiesConfigModule.create().addFile(file);
}
public static PropertiesConfigModule ofProperties(Properties properties) {
return PropertiesConfigModule.create().addProperties(properties);
}
/**
* Adds configs from given file
*
* @param file a path to a file with configs
* @return this module with configs, contained in a file
* @throws IllegalArgumentException if an IOException occurred
*/
public PropertiesConfigModule addFile(String file) {
addFile(new File(file), false);
return this;
}
public PropertiesConfigModule addFile(File file) {
addFile(file, false);
return this;
}
/**
* Adds configs from given file, if it exists
*
* @param file a path to a file with configs
* @return this module with configs, contained in file
*/
public PropertiesConfigModule addOptionalFile(String file) {
addFile(new File(file), true);
return this;
}
public PropertiesConfigModule addOptionalFile(File file) {
addFile(file, true);
return this;
}
private PropertiesConfigModule addFile(final File file, final boolean optional) {
properties.add(new Provider<Properties>() {
@Override
public Properties get() {
try (InputStream fis = new BufferedInputStream(new FileInputStream(file))) {
Properties property = new Properties();
property.load(fis);
return property;
} catch (IOException e) {
if (optional) {
logger.warn("Can't load properties file: {}", file);
return null;
} else {
throw new IllegalArgumentException(e);
}
}
}
;
});
return this;
}
public PropertiesConfigModule addProperties(Properties properties) {
this.properties.add(Providers.of(properties));
return this;
}
public PropertiesConfigModule saveEffectiveConfigTo(File file) {
this.saveFile = file;
return this;
}
public PropertiesConfigModule saveEffectiveConfigTo(String file) {
return saveEffectiveConfigTo(new File(file));
}
private class ConfigSaveService implements BlockingService {
@Override
public void start() throws Exception {
logger.info("Saving resulting config to {}", saveFile);
root.saveToPropertiesFile(saveFile);
}
@Override
public void stop() throws Exception {
}
}
@Override
protected void configure() {
if (saveFile != null) {
bind(ConfigSaveService.class).toInstance(new ConfigSaveService());
}
}
@Provides
@Singleton
Config provideConfig() {
List<Config> configs = new ArrayList<>();
for (Provider<Properties> propertiesProvider : properties) {
Properties properties = propertiesProvider.get();
if (properties != null) {
Config config = Config.ofProperties(properties);
configs.add(config);
}
}
root = Config.union(configs);
return root;
}
}