001/* 002 * JGrapes Event Driven Framework 003 * Copyright (C) 2017-2018 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.FileConfig; 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 YAML file. The YAML object described by this file 034 * represents the root directory. If an entry does not start with a 035 * slash, it represents the key of a key value pair. If it does 036 * starts with a slash, the value is another JSON object that 037 * describes the respective subdirectory. 038 * 039 * The component reads the initial values from {@link File} passed 040 * to the constructor. During application bootstrap, it 041 * intercepts the {@link Start} event using a handler with priority 042 * 999999. When receiving this event, it fires all known preferences 043 * values on the channels of the start event as a 044 * {@link InitialPreferences} event, using a new {@link EventPipeline} 045 * and waiting for its completion. Then, allows the intercepted 046 * {@link Start} event to continue. 047 * 048 * Components that depend on configuration values define handlers 049 * for {@link ConfigurationUpdate} events and adapt themselves to the values 050 * received. Note that due to the intercepted {@link Start} event, the initial 051 * preferences values are received before the {@link Start} event, so 052 * components' configurations can be rearranged before they actually 053 * start doing something. 054 * 055 * Besides initially publishing the stored preferences values, 056 * the component also listens for {@link ConfigurationUpdate} events 057 * on its channel and updates the YAML file (may be suppressed). 058 */ 059@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.AvoidDuplicateLiterals", 060 "PMD.GodClass" }) 061public class YamlConfigurationStore extends NightConfigStore { 062 063 /** 064 * Creates a new component with its channel set to the given 065 * channel and the given file. The component handles 066 * {@link ConfigurationUpdate} events and {@link FileChanged} 067 * events for the configuration file (see 068 * @link #NightConfigStore(Channel, File, boolean, boolean)} 069 * 070 * @param componentChannel the channel 071 * @param file the file used to store the configuration 072 * @throws IOException 073 */ 074 public YamlConfigurationStore(Channel componentChannel, File file) 075 throws IOException { 076 this(componentChannel, file, true); 077 } 078 079 /** 080 * Creates a new component with its channel set to the given 081 * channel and the given file. The component handles 082 * {@link FileChanged} events for the configuration file (see 083 * @link #NightConfigStore(Channel, File, boolean, boolean)} 084 * 085 * If `update` is `true`, the configuration file is updated 086 * when {@link ConfigurationUpdate} events are received. 087 * 088 * @param componentChannel the channel 089 * @param file the file used to store the configuration 090 * @param update if the configuration file is to be updated 091 * @throws IOException Signals that an I/O exception has occurred. 092 */ 093 @SuppressWarnings("PMD.ShortVariable") 094 public YamlConfigurationStore(Channel componentChannel, File file, 095 boolean update) throws IOException { 096 this(componentChannel, file, update, true); 097 } 098 099 /** 100 * Creates a new component with its channel set to the given 101 * channel and the given file. 102 * 103 * If `update` is `true`, the configuration file is updated 104 * when {@link ConfigurationUpdate} events are received. 105 * 106 * If `watch` is `true`, {@link FileChanged} events are processed 107 * and the configuration file is reloaded when it changes. Note 108 * that the generation of the {@link FileChanged} events must 109 * be configured independently (see {@link FileSystemWatcher}). 110 * 111 * @param componentChannel the channel 112 * @param file the file used to store the configuration 113 * @param update if the configuration file is to be updated 114 * @param watch if {@link FileChanged} events are to be processed 115 * @throws IOException Signals that an I/O exception has occurred. 116 */ 117 @SuppressWarnings("PMD.ShortVariable") 118 public YamlConfigurationStore(Channel componentChannel, File file, 119 boolean update, boolean watch) throws IOException { 120 super(componentChannel, file, update, watch); 121 // Force load of YamlFormat and its registration. Required for OSGi. 122 com.electronwill.nightconfig.yaml.YamlFormat.defaultInstance(); 123 config = FileConfig.builder(file.getAbsolutePath()).sync().concurrent() 124 .build(); 125 config.load(); 126 } 127 128}