001/* 002 * JGrapes Event Driven Framework 003 * Copyright (C) 2022 Michael N. Lipp 004 * 005 * This program is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU Affero General Public License as published by 007 * the Free Software Foundation; either version 3 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, but 011 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 012 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License 013 * for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License along 016 * with this program; if not, see <http://www.gnu.org/licenses/>. 017 */ 018 019package org.jgrapes.util; 020 021import com.electronwill.nightconfig.core.file.CommentedFileConfig; 022import java.io.File; 023import java.io.IOException; 024import org.jgrapes.core.Channel; 025import org.jgrapes.core.EventPipeline; 026import org.jgrapes.core.events.Start; 027import org.jgrapes.util.events.ConfigurationUpdate; 028import org.jgrapes.util.events.FileChanged; 029import org.jgrapes.util.events.InitialPreferences; 030 031/** 032 * This component provides a store for an application's configuration 033 * backed by a TOML file. 034 * 035 * The component reads the initial values from {@link File} passed 036 * to the constructor. During application bootstrap, it 037 * intercepts the {@link Start} event using a handler with priority 038 * 999999. When receiving this event, it fires all known preferences 039 * values on the channels of the start event as a 040 * {@link InitialPreferences} event, using a new {@link EventPipeline} 041 * and waiting for its completion. Then, allows the intercepted 042 * {@link Start} event to continue. 043 * 044 * Components that depend on configuration values define handlers 045 * for {@link ConfigurationUpdate} events and adapt themselves to the values 046 * received. Note that due to the intercepted {@link Start} event, the initial 047 * preferences values are received before the {@link Start} event, so 048 * components' configurations can be rearranged before they actually 049 * start doing something. 050 * 051 * Besides initially publishing the stored preferences values, 052 * the component also listens for {@link ConfigurationUpdate} events 053 * on its channel and updates the TOML file (may be suppressed). 054 */ 055@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.AvoidDuplicateLiterals", 056 "PMD.GodClass" }) 057public class TomlConfigurationStore extends NightConfigStore { 058 059 /** 060 * Creates a new component with its channel set to the given 061 * channel and the given file. The component handles 062 * {@link ConfigurationUpdate} events and {@link FileChanged} 063 * events for the configuration file (see 064 * @link #NightConfigStore(Channel, File, boolean, boolean)} 065 * 066 * @param componentChannel the channel 067 * @param file the file used to store the configuration 068 * @throws IOException 069 */ 070 public TomlConfigurationStore(Channel componentChannel, File file) 071 throws IOException { 072 this(componentChannel, file, true); 073 } 074 075 /** 076 * Creates a new component with its channel set to the given 077 * channel and the given file. The component handles 078 * {@link FileChanged} events for the configuration file (see 079 * @link #NightConfigStore(Channel, File, boolean, boolean)} 080 * 081 * If `update` is `true`, the configuration file is updated 082 * when {@link ConfigurationUpdate} events are received. 083 * 084 * @param componentChannel the channel 085 * @param file the file used to store the configuration 086 * @param update if the configuration file is to be updated 087 * @throws IOException Signals that an I/O exception has occurred. 088 */ 089 @SuppressWarnings("PMD.ShortVariable") 090 public TomlConfigurationStore(Channel componentChannel, File file, 091 boolean update) throws IOException { 092 this(componentChannel, file, update, true); 093 } 094 095 /** 096 * Creates a new component with its channel set to the given 097 * channel and the given file. 098 * 099 * If `update` is `true`, the configuration file is updated 100 * when {@link ConfigurationUpdate} events are received. 101 * 102 * If `watch` is `true`, {@link FileChanged} events are processed 103 * and the configuration file is reloaded when it changes. Note 104 * that the generation of the {@link FileChanged} events must 105 * be configured independently (see {@link FileSystemWatcher}). 106 * 107 * @param componentChannel the channel 108 * @param file the file used to store the configuration 109 * @param update if the configuration file is to be updated 110 * @param watch if {@link FileChanged} events are to be processed 111 * @throws IOException Signals that an I/O exception has occurred. 112 */ 113 @SuppressWarnings("PMD.ShortVariable") 114 public TomlConfigurationStore(Channel componentChannel, File file, 115 boolean update, boolean watch) throws IOException { 116 super(componentChannel, file, update, watch); 117 // Force load of TomlFormat and its registration. Required for OSGi. 118 com.electronwill.nightconfig.toml.TomlFormat.instance(); 119 config = CommentedFileConfig.builder(file.getAbsolutePath()).sync() 120 .concurrent().build(); 121 config.load(); 122 } 123 124}