diff --git a/CHANGELOG.md b/CHANGELOG.md index a172596..36da630 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] ### Added - Sonarcloud check +- Possibility to install a plugin via the frontpage ### Changed - CoffeeNet Starter Parent to version `0.38.2` diff --git a/src/main/java/coffee/synyx/frontpage/installer/PluginConfiguration.java b/src/main/java/coffee/synyx/frontpage/installer/PluginConfiguration.java new file mode 100644 index 0000000..1453d12 --- /dev/null +++ b/src/main/java/coffee/synyx/frontpage/installer/PluginConfiguration.java @@ -0,0 +1,17 @@ +package coffee.synyx.frontpage.installer; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author Tobias Schneider + */ +@Configuration +public class PluginConfiguration { + + @Bean + PluginConfigurationProperties pluginConfigurationProperties() { + + return new PluginConfigurationProperties(); + } +} diff --git a/src/main/java/coffee/synyx/frontpage/installer/PluginConfigurationProperties.java b/src/main/java/coffee/synyx/frontpage/installer/PluginConfigurationProperties.java new file mode 100644 index 0000000..f148bcf --- /dev/null +++ b/src/main/java/coffee/synyx/frontpage/installer/PluginConfigurationProperties.java @@ -0,0 +1,24 @@ +package coffee.synyx.frontpage.installer; + +import org.hibernate.validator.constraints.NotEmpty; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.validation.annotation.Validated; + +/** + * @author Tobias Schneider + */ +@Validated +@ConfigurationProperties("frontpage.plugins") +public class PluginConfigurationProperties { + + @NotEmpty + private String directory = "./plugins"; + + public String getDirectory() { + return directory; + } + + public void setDirectory(String directory) { + this.directory = directory; + } +} diff --git a/src/main/java/coffee/synyx/frontpage/installer/PluginInstallationException.java b/src/main/java/coffee/synyx/frontpage/installer/PluginInstallationException.java new file mode 100644 index 0000000..ee86720 --- /dev/null +++ b/src/main/java/coffee/synyx/frontpage/installer/PluginInstallationException.java @@ -0,0 +1,11 @@ +package coffee.synyx.frontpage.installer; + +/** + * @author Tobias Schneider + */ +public class PluginInstallationException extends RuntimeException { + + PluginInstallationException(String message) { + super(message); + } +} diff --git a/src/main/java/coffee/synyx/frontpage/installer/PluginInstallerService.java b/src/main/java/coffee/synyx/frontpage/installer/PluginInstallerService.java new file mode 100644 index 0000000..bd525c5 --- /dev/null +++ b/src/main/java/coffee/synyx/frontpage/installer/PluginInstallerService.java @@ -0,0 +1,16 @@ +package coffee.synyx.frontpage.installer; + +/** + * @author Tobias Schneider + */ +public interface PluginInstallerService { + + /** + * Installs a plugin into the defined directory of `frontpage.plugins.directory` + * + * @param url of the plugin to download and install + * + * @throws PluginInstallationException if an error occurred + */ + void installPlugin(String url); +} diff --git a/src/main/java/coffee/synyx/frontpage/installer/SimplePluginInstallerService.java b/src/main/java/coffee/synyx/frontpage/installer/SimplePluginInstallerService.java new file mode 100644 index 0000000..be3bc29 --- /dev/null +++ b/src/main/java/coffee/synyx/frontpage/installer/SimplePluginInstallerService.java @@ -0,0 +1,82 @@ +package coffee.synyx.frontpage.installer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static java.lang.String.format; +import static java.lang.invoke.MethodHandles.lookup; +import static org.springframework.http.HttpMethod.GET; +import static org.springframework.http.HttpStatus.OK; + +/** + * @author Tobias Schneider + */ +@Service +public class SimplePluginInstallerService implements PluginInstallerService { + + private static final Logger LOGGER = LoggerFactory.getLogger(lookup().lookupClass()); + + private final RestTemplate restTemplate; + private final PluginConfigurationProperties pluginConfigurationProperties; + + @Autowired + public SimplePluginInstallerService(RestTemplate restTemplate, PluginConfigurationProperties pluginConfigurationProperties) { + + this.restTemplate = restTemplate; + this.pluginConfigurationProperties = pluginConfigurationProperties; + } + + @Override + public void installPlugin(String url) { + + HttpHeaders headers = new HttpHeaders(); + HttpEntity entity = new HttpEntity<>(headers); + + ResponseEntity response = restTemplate.exchange(url, GET, entity, byte[].class); + + if (response.getStatusCode() == OK) { + + createPluginDirectory(); + + Path pluginUri = Paths.get(pluginConfigurationProperties.getDirectory(), getPluginName(url)); + try { + Files.write(pluginUri, response.getBody()); + } catch (IOException e) { + throw new PluginInstallationException(format("IO Error occurred while saving the plugin to %s", pluginUri)); + } + } + } + + private void createPluginDirectory() { + + if (new File(pluginConfigurationProperties.getDirectory()).mkdirs()) { + LOGGER.debug("{} was created", pluginConfigurationProperties.getDirectory()); + } + } + + private String getPluginName(String url) { + + String path; + try { + path = new URI(url).getPath(); + } catch (URISyntaxException e) { + throw new PluginInstallationException("Url of plugin is in wrong format and violates RFC 2396"); + } + + return path.substring(path.lastIndexOf('/') + 1); + } +} diff --git a/src/test/java/coffee/synyx/frontpage/installer/SimplePluginInstallerServiceIT.java b/src/test/java/coffee/synyx/frontpage/installer/SimplePluginInstallerServiceIT.java new file mode 100644 index 0000000..c499211 --- /dev/null +++ b/src/test/java/coffee/synyx/frontpage/installer/SimplePluginInstallerServiceIT.java @@ -0,0 +1,61 @@ +package coffee.synyx.frontpage.installer; + + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.web.client.RestTemplate; + +import java.io.File; + +import static org.assertj.core.api.Assertions.assertThat; + + +/** + * @author Tobias Schneider + */ +@RunWith(SpringRunner.class) +public class SimplePluginInstallerServiceIT { + + private PluginInstallerService sut; + + @Before + public void setUp() { + + final PluginConfigurationProperties pluginConfigurationProperties = new PluginConfigurationProperties(); + + sut = new SimplePluginInstallerService(new RestTemplate(), pluginConfigurationProperties); + } + + @After + public void tearDown() { + + deleteDirectory(new File("./plugins")); + } + + + @Test + public void downloadPlugin() { + String url = "https://github.com/coffeenet/coffeenet-frontpage-plugin-feed/releases/download/0.2.0/" + + "frontpage-plugin-feed-0.2.0-jar-with-dependencies.jar"; + + sut.installPlugin(url); + + File plugin = new File("./plugins/frontpage-plugin-feed-0.2.0-jar-with-dependencies.jar"); + + assertThat(plugin.exists()).isTrue(); + assertThat(plugin.isFile()).isTrue(); + } + + private boolean deleteDirectory(File directoryToBeDeleted) { + File[] allContents = directoryToBeDeleted.listFiles(); + if (allContents != null) { + for (File file : allContents) { + deleteDirectory(file); + } + } + return directoryToBeDeleted.delete(); + } +}