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}