-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #38 from Toparvion/choices-auto-reload
#33 Auto-reload for choices properties
- Loading branch information
Showing
23 changed files
with
664 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,4 +13,5 @@ out/ | |
memo.txt | ||
log-samples/ | ||
work/ | ||
/dist/ | ||
/dist/ | ||
log/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
43 changes: 43 additions & 0 deletions
43
src/main/java/tech/toparvion/analog/model/config/ChoicesAutoReloadProperties.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package tech.toparvion.analog.model.config; | ||
|
||
import org.springframework.boot.context.properties.ConfigurationProperties; | ||
import org.springframework.stereotype.Component; | ||
import tech.toparvion.analog.service.choice.ConditionalOnChoicesAutoReloadEnabled; | ||
import tech.toparvion.analog.util.config.ChoicesCustomConfigurationLoader; | ||
|
||
/** | ||
* @author Polyudov | ||
* @since v0.14 | ||
*/ | ||
@Component | ||
@SuppressWarnings("unused") // setters are required by Spring Boot | ||
@ConfigurationProperties("choices-source") | ||
public class ChoicesAutoReloadProperties { | ||
/** | ||
* Path to custom {@linkplain ChoiceProperties choices} location | ||
* | ||
* @see ChoicesCustomConfigurationLoader | ||
*/ | ||
private String location; | ||
|
||
/** | ||
* This field {@linkplain ConditionalOnChoicesAutoReloadEnabled used} for enable/disable auto reloading | ||
*/ | ||
private boolean autoReloadEnabled = false; | ||
|
||
public String getLocation() { | ||
return location; | ||
} | ||
|
||
public void setLocation(String location) { | ||
this.location = location; | ||
} | ||
|
||
public boolean isAutoReloadEnabled() { | ||
return autoReloadEnabled; | ||
} | ||
|
||
public void setAutoReloadEnabled(boolean autoReloadEnabled) { | ||
this.autoReloadEnabled = autoReloadEnabled; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
100 changes: 100 additions & 0 deletions
100
src/main/java/tech/toparvion/analog/service/choice/ChoicesAutoReloadConfiguration.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
package tech.toparvion.analog.service.choice; | ||
|
||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.scheduling.concurrent.CustomizableThreadFactory; | ||
import tech.toparvion.analog.model.config.ChoicesAutoReloadProperties; | ||
|
||
import javax.annotation.Nullable; | ||
import java.io.IOException; | ||
import java.nio.file.FileSystems; | ||
import java.nio.file.Path; | ||
import java.nio.file.Paths; | ||
import java.nio.file.WatchService; | ||
import java.util.Objects; | ||
import java.util.concurrent.Executor; | ||
|
||
import static com.google.common.base.Strings.isNullOrEmpty; | ||
import static java.nio.file.Files.isDirectory; | ||
import static java.nio.file.Files.isReadable; | ||
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; | ||
import static java.util.Objects.requireNonNullElse; | ||
import static java.util.concurrent.Executors.newSingleThreadExecutor; | ||
|
||
/** | ||
* Configuration for choices list auto reloading | ||
* | ||
* @author Polyudov | ||
* @since v0.14 | ||
*/ | ||
@Configuration | ||
@ConditionalOnChoicesAutoReloadEnabled | ||
class ChoicesAutoReloadConfiguration { | ||
private static final Logger log = LoggerFactory.getLogger(ChoicesAutoReloadConfiguration.class); | ||
static final String CHOICES_AUTO_RELOAD_EXECUTOR = "choicesAutoReloadExecutor"; | ||
|
||
@Bean(CHOICES_AUTO_RELOAD_EXECUTOR) | ||
Executor choicesAutoReloadExecutor() { | ||
return newSingleThreadExecutor(new CustomizableThreadFactory("auto-reload-")); | ||
} | ||
|
||
@Bean | ||
@Nullable | ||
FileWatcherProvider fileWatcherProvider(ChoicesAutoReloadProperties choiceProperties) { | ||
String choicesPropertiesLocation = choiceProperties.getLocation(); | ||
if (isNullOrEmpty(choicesPropertiesLocation)) { | ||
log.info("Custom path for choices list is not present in 'choices-source.location' property. Auto reload logic won't be applied."); | ||
return null; | ||
} | ||
|
||
try { | ||
Path choicesPropertiesPath = Paths.get(choicesPropertiesLocation); //check exception | ||
|
||
if (!isReadable(choicesPropertiesPath)) { | ||
log.warn("File '{}' does not exist or there is no read access to this file", choicesPropertiesLocation); | ||
return null; | ||
} | ||
if (isDirectory(choicesPropertiesPath)) { | ||
log.warn("'choices-source.location' ('{}') is a directory. Please specify a path to a regular file to enable choices auto reloading.", choicesPropertiesLocation); | ||
return null; | ||
} | ||
return new FileWatcherProvider(choicesPropertiesPath); | ||
} catch (Exception e) { | ||
log.warn("Can`t create choices list watcher", e); | ||
return null; | ||
} | ||
} | ||
|
||
static class FileWatcherProvider { | ||
private final Path watchDir; | ||
private final Path choicesPropertiesPath; | ||
private final WatchService watchService; | ||
|
||
FileWatcherProvider(Path choicesPropertiesPath) throws IOException { | ||
this.choicesPropertiesPath = choicesPropertiesPath; | ||
this.watchDir = fetchParent(choicesPropertiesPath); | ||
this.watchService = FileSystems.getDefault().newWatchService(); | ||
|
||
watchDir.register(watchService, ENTRY_MODIFY); | ||
} | ||
|
||
public WatchService getWatchService() { | ||
return watchService; | ||
} | ||
|
||
public Path getChoicesPropertiesPath() { | ||
return choicesPropertiesPath; | ||
} | ||
|
||
public boolean isChoicesPropertiesFileEvent(Path eventPath) { | ||
Path eventResolvedPath = watchDir.resolve(eventPath); | ||
return Objects.equals(choicesPropertiesPath, eventResolvedPath); | ||
} | ||
|
||
private Path fetchParent(Path path) { | ||
return requireNonNullElse(path.getParent(), path); | ||
} | ||
} | ||
} |
74 changes: 74 additions & 0 deletions
74
src/main/java/tech/toparvion/analog/service/choice/ChoicesPropertiesChangesListener.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
package tech.toparvion.analog.service.choice; | ||
|
||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.boot.context.event.ApplicationReadyEvent; | ||
import org.springframework.cloud.endpoint.RefreshEndpoint; | ||
import org.springframework.context.event.EventListener; | ||
import org.springframework.scheduling.annotation.Async; | ||
import org.springframework.scheduling.annotation.EnableAsync; | ||
import org.springframework.stereotype.Service; | ||
import tech.toparvion.analog.service.choice.ChoicesAutoReloadConfiguration.FileWatcherProvider; | ||
|
||
import javax.annotation.Nullable; | ||
import java.nio.file.*; | ||
|
||
import static java.nio.file.StandardWatchEventKinds.OVERFLOW; | ||
import static tech.toparvion.analog.service.choice.ChoicesAutoReloadConfiguration.CHOICES_AUTO_RELOAD_EXECUTOR; | ||
|
||
/** | ||
* @author Polyudov | ||
* @since v0.14 | ||
*/ | ||
@Service | ||
@EnableAsync | ||
@ConditionalOnChoicesAutoReloadEnabled | ||
class ChoicesPropertiesChangesListener { | ||
private static final Logger log = LoggerFactory.getLogger(ChoicesPropertiesChangesListener.class); | ||
|
||
private final RefreshEndpoint refreshEndpoint; | ||
private final FileWatcherProvider fileWatcherProvider; | ||
|
||
@Autowired | ||
ChoicesPropertiesChangesListener(RefreshEndpoint refreshEndpoint, | ||
@Nullable FileWatcherProvider fileWatcherProvider) { | ||
this.refreshEndpoint = refreshEndpoint; | ||
this.fileWatcherProvider = fileWatcherProvider; | ||
} | ||
|
||
@Async(CHOICES_AUTO_RELOAD_EXECUTOR) | ||
@EventListener(ApplicationReadyEvent.class) | ||
public void watchFile() { | ||
if (fileWatcherProvider == null) { | ||
return; | ||
} | ||
try { | ||
log.info("Start watching for the choices properties file: '{}'", fileWatcherProvider.getChoicesPropertiesPath().toAbsolutePath()); | ||
WatchService watchService = fileWatcherProvider.getWatchService(); | ||
WatchKey key; | ||
while ((key = watchService.take()) != null) { | ||
for (WatchEvent<?> event : key.pollEvents()) { | ||
//Filter some special events and repeated events | ||
if (OVERFLOW.equals(event.kind()) || event.count() > 1) { | ||
continue; | ||
} | ||
Path path = (Path) event.context(); | ||
//Check modified file | ||
if (fileWatcherProvider.isChoicesPropertiesFileEvent(path)) { | ||
refreshEndpoint.refresh(); | ||
log.info("Choices properties file was reloaded"); | ||
} | ||
} | ||
boolean reset = key.reset(); | ||
if (!reset) { | ||
log.error("Choices properties changes watching is now invalid"); | ||
throw new ClosedWatchServiceException(); | ||
} | ||
} | ||
} catch (ClosedWatchServiceException ignored) { //this exception throws during a normal application shutdown | ||
} catch (Exception e) { | ||
log.error("Stop watching for the choices properties file because of error:", e); | ||
} | ||
} | ||
} |
25 changes: 25 additions & 0 deletions
25
...main/java/tech/toparvion/analog/service/choice/ConditionalOnChoicesAutoReloadEnabled.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package tech.toparvion.analog.service.choice; | ||
|
||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | ||
import org.springframework.context.annotation.Conditional; | ||
|
||
import java.lang.annotation.Documented; | ||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.Target; | ||
|
||
import static java.lang.annotation.ElementType.TYPE; | ||
import static java.lang.annotation.RetentionPolicy.RUNTIME; | ||
|
||
/** | ||
* Custom {@link Conditional @Conditional} that checks if choices <code>auto reload</code> enabled in <code>properties</code>.<br/> | ||
* This is some kind of "syntax sugar" which is designed to add this condition to all required places. | ||
* | ||
* @author Polyudov | ||
* @since v0.14 | ||
*/ | ||
@Documented | ||
@Target(TYPE) | ||
@Retention(RUNTIME) | ||
@SuppressWarnings("DefaultAnnotationParam") | ||
@ConditionalOnProperty(name = "choices-source.auto-reload-enabled", havingValue = "true", matchIfMissing = false) | ||
public @interface ConditionalOnChoicesAutoReloadEnabled {} |
Oops, something went wrong.