11package io .kafbat .ui .service .app ;
22
3- import io .kafbat .ui .util .ApplicationRestarter ;
4- import io .kafbat .ui .util .DynamicConfigOperations ;
3+ import io .kafbat .ui .util .MultiFileWatcher ;
54import jakarta .annotation .PostConstruct ;
65import jakarta .annotation .PreDestroy ;
76import java .io .IOException ;
8- import java .nio .file .ClosedWatchServiceException ;
9- import java .nio .file .FileSystems ;
10- import java .nio .file .Files ;
11- import java .nio .file .Path ;
12- import java .nio .file .StandardWatchEventKinds ;
13- import java .nio .file .WatchEvent ;
14- import java .nio .file .WatchKey ;
15- import java .nio .file .WatchService ;
16- import java .util .Arrays ;
17- import java .util .HashMap ;
18- import java .util .Iterator ;
19- import java .util .Map ;
7+ import java .nio .file .Paths ;
8+ import java .util .LinkedHashSet ;
209import java .util .Objects ;
2110import java .util .stream .Collectors ;
11+ import java .util .stream .Stream ;
12+ import java .util .stream .StreamSupport ;
2213import lombok .RequiredArgsConstructor ;
2314import lombok .extern .slf4j .Slf4j ;
24- import org .springframework .boot .autoconfigure .condition .ConditionalOnProperty ;
2515import org .springframework .boot .env .OriginTrackedMapPropertySource ;
2616import org .springframework .boot .origin .Origin ;
27- import org .springframework .boot .origin .OriginLookup ;
2817import org .springframework .boot .origin .OriginTrackedValue ;
2918import org .springframework .boot .origin .TextResourceOrigin ;
30- import org .springframework .context .ApplicationContext ;
31- import org .springframework .core .env .AbstractEnvironment ;
3219import org .springframework .core .env .ConfigurableEnvironment ;
33- import org .springframework .core .env .EnumerablePropertySource ;
34- import org .springframework .core .env .MapPropertySource ;
35- import org .springframework .core .env .PropertySource ;
20+ import org .springframework .core .io .Resource ;
3621import org .springframework .stereotype .Service ;
37- import org .stringtemplate .v4 .ST ;
3822
3923@ Service
4024//@ConditionalOnProperty(value = "dynamic.config.autoreload", havingValue = "true")
4327public class ConfigReloadService {
4428
4529 private static final String THREAD_NAME = "config-watcher-thread" ;
46- private static final long STARTUP_SUPPRESSION_MS = 1000 ;
47- private final long appStartedAt = System .currentTimeMillis ();
4830
49- private final DynamicConfigOperations dynamicConfigOperations ;
50- private final ApplicationRestarter restarter ;
31+ private final ConfigurableEnvironment environment ;
5132
52- private WatchService watchService ;
5333 private Thread watcherThread ;
54-
55- private final ApplicationContext context ;
56- private final ConfigurableEnvironment environment ;
34+ private MultiFileWatcher multiFileWatcher ;
5735
5836 @ PostConstruct
5937 public void init () {
60-
61- var o = environment .getPropertySources ()
62- .stream ()
63- .filter (ps -> ps instanceof OriginTrackedMapPropertySource )
64- .map (ps -> (OriginTrackedMapPropertySource ) ps )
65- .collect (Collectors .toUnmodifiableList ())
66- .stream ()
67- .findFirst ()
68- .get ()
69- .getSource ()
70- .values ()
71- .stream ()
72- .findFirst ()
73- .map (a -> (OriginTrackedValue ) a )
74- .get ()
75- .getOrigin ();
76-
77- var origin = (TextResourceOrigin ) o ;
78-
79- origin .getResource ();
80-
81- /*
82- environment.getPropertySources()
83- .stream()
84- .filter(ps -> ps instanceof OriginTrackedMapPropertySource)
85- .map(ps -> (OriginTrackedMapPropertySource)ps)
86- .map(ps -> ps.getSource())
87- .map(source -> source.values())
88- .map(values -> {
89- return (HashMap<String, String>) values;
90- })
91- // .map(sourceValues -> sourceValues.)
92- .collect(Collectors.toUnmodifiableList());
93- */
94-
95-
96- // =============
97-
98- /* environment.getPropertySources().stream()
99- .filter(ps -> ps instanceof EnumerablePropertySource)
100- .filter(ps -> ps instanceof OriginLookup)
101- .flatMap(ps -> {
102- EnumerablePropertySource<?> eps = (EnumerablePropertySource<?>) ps;
103- OriginLookup<String> lookup = (OriginLookup<String>) ps;
104- return Arrays.stream(eps.getPropertyNames())
105- .map(name -> {
106- Origin origin = lookup.getOrigin(name);
107- return origin != null ? origin.toString() : null;
108- });
38+ var propertySourcePaths = StreamSupport .stream (environment .getPropertySources ().spliterator (), false )
39+ .filter (OriginTrackedMapPropertySource .class ::isInstance )
40+ .map (OriginTrackedMapPropertySource .class ::cast )
41+ .flatMap (ps -> ps .getSource ().values ().stream ())
42+ .map (v -> (v instanceof OriginTrackedValue otv ) ? otv .getOrigin () : null )
43+ .filter (Objects ::nonNull )
44+ .flatMap (o -> Stream .iterate (o , Objects ::nonNull , Origin ::getParent ))
45+ .filter (TextResourceOrigin .class ::isInstance )
46+ .map (TextResourceOrigin .class ::cast )
47+ .map (TextResourceOrigin ::getResource )
48+ .filter (Objects ::nonNull )
49+ .filter (Resource ::exists )
50+ .filter (Resource ::isReadable )
51+ .filter (Resource ::isFile )
52+ .map (r -> {
53+ try {
54+ return r .getURI ();
55+ } catch (IOException e ) {
56+ log .error ("can't retrieve resource URL" , e );
57+ return null ;
58+ }
10959 })
11060 .filter (Objects ::nonNull )
111- .distinct()
112- .collect(Collectors.toUnmodifiableList());*/
113-
114- // ===============
115-
116- /* Map<String, Object> map = new HashMap();
117- for(Iterator it = ((AbstractEnvironment) environment).getPropertySources().iterator(); it.hasNext(); ) {
118- PropertySource propertySource = (PropertySource) it.next();
119- if (propertySource instanceof MapPropertySource) {
120- map.putAll(((MapPropertySource) propertySource).getSource());
121- }
122- }*/
123-
124- // ====
125-
126- /* SpringConfigurableEnvironment properties = new SpringConfigurableEnvironment(springEnv);
127- SpringConfigurableEnvironment.PropertyInfo info = properties.get("profile.env");
128- assertEquals("default", properties.get(info.getValue());
129- assertEquals(
130- "Config resource 'class path resource [application.properties]' via location 'optional:classpath:/'",
131- info.getSourceList.get(0));*/
61+ .map (Paths ::get )
62+ .collect (Collectors .toCollection (LinkedHashSet ::new ));
13263
133-
134-
135-
136- System .out .println ();
137- // environment.getPropertySources()
138- // .stream()
139-
140- var configPath = dynamicConfigOperations .dynamicConfigFilePath ();
141- if (!Files .exists (configPath ) || !Files .isReadable (configPath )) {
142- log .warn ("Dynamic config file {} doesnt exist or is not readable. Auto reload is disabled" , configPath );
64+ if (propertySourcePaths .isEmpty ()) {
65+ log .debug ("No config files found, auto reload is disabled" );
14366 return ;
14467 }
14568
14669 log .debug ("Auto reload is enabled, will watch for config changes" );
14770
14871 try {
149- registerWatchService ();
150- startWatching ();
72+ this .multiFileWatcher = new MultiFileWatcher (propertySourcePaths , this ::reload );
73+ this .watcherThread = new Thread (multiFileWatcher ::watchLoop , THREAD_NAME );
74+ this .watcherThread .start ();
15175 } catch (IOException e ) {
15276 log .error ("Error while registering watch service" , e );
15377 }
@@ -156,8 +80,8 @@ public void init() {
15680 @ PreDestroy
15781 public void shutdown () {
15882 try {
159- if (watchService != null ) {
160- watchService .close ();
83+ if (multiFileWatcher != null ) {
84+ multiFileWatcher .close ();
16185 }
16286 } catch (IOException ignored ) {
16387 }
@@ -166,56 +90,8 @@ public void shutdown() {
16690 }
16791 }
16892
169- private void registerWatchService () throws IOException {
170- this .watchService = FileSystems .getDefault ().newWatchService ();
171- dynamicConfigOperations .dynamicConfigFilePath ()
172- .getParent ()
173- .register (watchService , StandardWatchEventKinds .ENTRY_MODIFY );
174- }
175-
176- private void startWatching () {
177- watcherThread = new Thread (this ::watchLoop , THREAD_NAME );
178- watcherThread .start ();
179- }
180-
181- private void watchLoop () {
182- final var watchedDir = dynamicConfigOperations .dynamicConfigFilePath ().getParent ();
183-
184- while (true ) {
185- try {
186- WatchKey key = watchService .take ();
187- for (WatchEvent <?> event : key .pollEvents ()) {
188- WatchEvent .Kind <?> kind = event .kind ();
189- Path changed = watchedDir .resolve ((Path ) event .context ());
190-
191- if (kind != StandardWatchEventKinds .ENTRY_MODIFY ) {
192- continue ;
193- }
194- if (!changed .equals (dynamicConfigOperations .dynamicConfigFilePath ())) {
195- continue ;
196- }
197-
198- var now = System .currentTimeMillis ();
199- if (now - appStartedAt < STARTUP_SUPPRESSION_MS ) {
200- continue ;
201- }
202-
203- restart ();
204- }
205- key .reset ();
206- } catch (ClosedWatchServiceException e ) {
207- log .trace ("Watch service closed, exiting watcher thread" );
208- break ;
209- } catch (InterruptedException e ) {
210- Thread .currentThread ().interrupt ();
211- break ;
212- }
213- }
214- }
93+ private void reload () {
21594
216- private void restart () {
217- log .info ("Application config change detected, restarting" );
218- restarter .requestRestart ();
21995 }
22096
22197
0 commit comments