diff --git a/build.gradle b/build.gradle index 8ffb212..d3fcc76 100644 --- a/build.gradle +++ b/build.gradle @@ -18,6 +18,8 @@ ext { slf4jVersion = "1.7.+" junitVersion = '5.+' mockitoVersion = "3.+" + gsonVersion = "2.8.6" + wireMockVersion = "2.25.1" } @@ -70,10 +72,13 @@ dependencies { "javax.xml.bind:jaxb-api:2.2.11", "com.sun.xml.bind:jaxb-core:2.2.11", "com.sun.xml.bind:jaxb-impl:2.2.11", - "javax.activation:activation:1.1.1" + "javax.activation:activation:1.1.1", + "org.web3j:hosted-providers:$web3jVersion", + "com.google.code.gson:gson:$gsonVersion" runtime "org.slf4j:slf4j-nop:$slf4jVersion" testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion", - "org.mockito:mockito-core:$mockitoVersion" + "org.mockito:mockito-core:$mockitoVersion", + "com.github.tomakehurst:wiremock-jre8:$wireMockVersion" } diff --git a/gradle/jacoco/build.gradle b/gradle/jacoco/build.gradle index 8e568d0..1bfe20b 100644 --- a/gradle/jacoco/build.gradle +++ b/gradle/jacoco/build.gradle @@ -18,8 +18,7 @@ task jacocoRootTestReport(type: org.gradle.testing.jacoco.tasks.JacocoReport) { exclude: [ 'org/web3j/abi/datatypes/generated/**', 'org/web3j/tuples/generated/**', - 'org/web3j/ens/contracts/generated/**', - 'org/gradle/**' + 'org/web3j/ens/contracts/generated/**' ]) })) } diff --git a/src/main/java/org/web3j/console/Runner.java b/src/main/java/org/web3j/console/Runner.java index a3bc130..709ae6a 100644 --- a/src/main/java/org/web3j/console/Runner.java +++ b/src/main/java/org/web3j/console/Runner.java @@ -15,9 +15,11 @@ import org.web3j.codegen.Console; import org.web3j.codegen.SolidityFunctionWrapperGenerator; import org.web3j.codegen.TruffleJsonFunctionWrapperGenerator; +import org.web3j.console.config.CliConfig; import org.web3j.console.project.ProjectCreator; import org.web3j.console.project.ProjectImporter; import org.web3j.console.project.UnitTestCreator; +import org.web3j.console.update.Updater; import org.web3j.utils.Version; import static org.web3j.codegen.SolidityFunctionWrapperGenerator.COMMAND_SOLIDITY; @@ -46,6 +48,13 @@ public class Runner { public static void main(String[] args) throws Exception { System.out.println(LOGO); + CliConfig config = CliConfig.getConfig(CliConfig.getWeb3jConfigPath().toFile()); + Updater updater = new Updater(config); + updater.promptIfUpdateAvailable(); + Thread updateThread = new Thread(updater::onlineUpdateCheck); + updateThread.setDaemon(true); + updateThread.start(); + if (args.length < 1) { Console.exitError(USAGE); } else { @@ -83,5 +92,7 @@ public static void main(String[] args) throws Exception { Console.exitError(USAGE); } } + + config.save(); } } diff --git a/src/main/java/org/web3j/console/config/CliConfig.java b/src/main/java/org/web3j/console/config/CliConfig.java new file mode 100644 index 0000000..dfc78af --- /dev/null +++ b/src/main/java/org/web3j/console/config/CliConfig.java @@ -0,0 +1,153 @@ +/* + * Copyright 2019 Web3 Labs Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.web3j.console.config; + +import java.io.*; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.UUID; + +import com.google.gson.Gson; + +import org.web3j.utils.Version; + +public class CliConfig { + private static final Path web3jConfigPath = + Paths.get(System.getProperty("user.home"), ".web3j", ".config"); + private static final String defaultServicesUrl = "https://internal.services.web3labs.com"; + + public static Path getWeb3jConfigPath() { + return web3jConfigPath; + } + + private static CliConfig initializeDefaultConfig(File configFile) throws IOException { + File web3jHome = new File(configFile.getParent()); + if (!web3jHome.exists() && !web3jHome.mkdirs()) { + throw new IOException("Failed to create Web3j home directory"); + } + return new CliConfig( + Version.getVersion(), + defaultServicesUrl, + UUID.randomUUID().toString(), + Version.getVersion(), + null); + } + + private static CliConfig getSavedConfig(File configFile) throws IOException { + String configContents = new String(Files.readAllBytes(configFile.toPath())); + return new Gson().fromJson(configContents, CliConfig.class); + } + + public static CliConfig getConfig(File configFile) throws IOException { + if (configFile.exists()) { + return getSavedConfig(configFile); + } else { + return initializeDefaultConfig(configFile); + } + } + + public String getLatestVersion() { + return latestVersion; + } + + public void setLatestVersion(String latestVersion) { + this.latestVersion = latestVersion; + } + + public boolean isUpdateAvailable() { + return !version.equals(latestVersion); + } + + public enum OS { + DARWIN, + FREEBSD, + OPENBSD, + LINUX, + SOLARIS, + WINDOWS, + AIX, + UNKNOWN; + + @Override + public String toString() { + return name().toLowerCase(); + } + } + + public static OS determineOS() { + String osName = System.getProperty("os.name").split(" ")[0].toLowerCase(); + if (osName.startsWith("mac") || osName.startsWith("darwin")) { + return OS.DARWIN; + } else if (osName.startsWith("linux")) { + return OS.LINUX; + } else if (osName.startsWith("sunos") || osName.startsWith("solaris")) { + return OS.SOLARIS; + } else if (osName.startsWith("aix")) { + return OS.AIX; + } else if (osName.startsWith("openbsd")) { + return OS.OPENBSD; + } else if (osName.startsWith("freebsd")) { + return OS.FREEBSD; + } else if (osName.startsWith("windows")) { + return OS.WINDOWS; + } else { + return OS.UNKNOWN; + } + } + + private String version; + private String servicesUrl; + private String clientId; + private String latestVersion; + private String updatePrompt; + + public CliConfig( + String version, + String servicesUrl, + String clientId, + String latestVersion, + String updatePrompt) { + this.version = version; + this.servicesUrl = servicesUrl; + this.clientId = clientId; + this.latestVersion = latestVersion; + this.updatePrompt = updatePrompt; + } + + public String getVersion() { + return version; + } + + public String getServicesUrl() { + return servicesUrl; + } + + public String getClientId() { + return clientId; + } + + public String getUpdatePrompt() { + return updatePrompt; + } + + public void setUpdatePrompt(String updatePrompt) { + this.updatePrompt = updatePrompt; + } + + public void save() throws IOException { + String jsonToWrite = new Gson().toJson(this); + Files.write(web3jConfigPath, jsonToWrite.getBytes(Charset.defaultCharset())); + } +} diff --git a/src/main/java/org/web3j/console/update/Updater.java b/src/main/java/org/web3j/console/update/Updater.java new file mode 100644 index 0000000..8cb73cc --- /dev/null +++ b/src/main/java/org/web3j/console/update/Updater.java @@ -0,0 +1,84 @@ +/* + * Copyright 2019 Web3 Labs Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.web3j.console.update; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import okhttp3.MultipartBody; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +import org.web3j.console.config.CliConfig; +import org.web3j.utils.Version; + +public class Updater { + + private CliConfig config; + + public Updater(CliConfig config) { + this.config = config; + } + + public void promptIfUpdateAvailable() { + if (config.isUpdateAvailable()) { + System.out.println( + String.format( + "A new Web3j update is available. To update, run: %s", + config.getUpdatePrompt())); + } + } + + public void onlineUpdateCheck() { + OkHttpClient client = new OkHttpClient(); + + RequestBody updateBody = + new MultipartBody.Builder() + .setType(MultipartBody.FORM) + .addFormDataPart("os", CliConfig.determineOS().toString()) + .addFormDataPart("clientId", config.getClientId()) + .addFormDataPart("data", "update_check") + .build(); + + Request updateCheckRequest = + new okhttp3.Request.Builder() + .url(String.format("%s/api/versions/latest", config.getServicesUrl())) + .post(updateBody) + .build(); + + try { + Response sendRawResponse = client.newCall(updateCheckRequest).execute(); + JsonElement element; + if (sendRawResponse.code() == 200 + && sendRawResponse.body() != null + && (element = JsonParser.parseString(sendRawResponse.body().string())) != null + && element.isJsonObject()) { + JsonObject rootObj = element.getAsJsonObject().get("latest").getAsJsonObject(); + String latestVersion = rootObj.get("version").getAsString(); + if (!latestVersion.equals(Version.getVersion())) { + config.setLatestVersion(latestVersion); + config.setUpdatePrompt( + rootObj.get( + CliConfig.determineOS() == CliConfig.OS.WINDOWS + ? "install_win" + : "install_unix") + .getAsString()); + config.save(); + } + } + } catch (Exception ignored) { + } + } +} diff --git a/src/main/resources/Template.java b/src/main/resources/Template.java index 04a9d8f..df8182a 100644 --- a/src/main/resources/Template.java +++ b/src/main/resources/Template.java @@ -1,8 +1,7 @@ package ; public class { + public static void main(String[] args) { -public static void main(String[]args) { - - } + } } \ No newline at end of file diff --git a/src/test/java/org/web3j/console/project/UpdaterTest.java b/src/test/java/org/web3j/console/project/UpdaterTest.java new file mode 100644 index 0000000..6204738 --- /dev/null +++ b/src/test/java/org/web3j/console/project/UpdaterTest.java @@ -0,0 +1,145 @@ +/* + * Copyright 2019 Web3 Labs Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.web3j.console.project; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.UUID; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.google.gson.Gson; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.Mockito; + +import org.web3j.console.config.CliConfig; +import org.web3j.console.update.Updater; +import org.web3j.utils.Version; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; + +public class UpdaterTest { + + private static Path tempWeb3jSettingsPath; + private static WireMockServer wireMockServer; + + @BeforeEach + void setup(@TempDir Path temp) { + tempWeb3jSettingsPath = Paths.get(temp.toString(), ".config"); + wireMockServer = new WireMockServer(wireMockConfig().port(8081)); + wireMockServer.start(); + WireMock.configureFor("localhost", wireMockServer.port()); + } + + @AfterEach + void tearDown() { + wireMockServer.stop(); + } + + @ParameterizedTest + @ValueSource(strings = {"4.5.6", "4.5.7"}) + void testUpdateCheckWorksSuccessfullyWhenUpdateAvailable(String version) throws Exception { + testWorksWithVersion(version); + } + + @Test + void testCurrentVersion() throws Exception { + testWorksWithVersion(Version.getVersion()); + } + + private void testWorksWithVersion(String version) throws IOException { + CliConfig config = + mock( + CliConfig.class, + Mockito.withSettings() + .useConstructor( + Version.getVersion(), + "http://localhost:8081", + UUID.randomUUID().toString(), + Version.getVersion(), + null) + .defaultAnswer(Mockito.CALLS_REAL_METHODS)); + + doAnswer( + invocation -> { + String jsonToWrite = + new Gson() + .toJson( + new CliConfig( + config.getVersion(), + config.getServicesUrl(), + config.getClientId(), + config.getLatestVersion(), + config.getUpdatePrompt())); + Files.write( + tempWeb3jSettingsPath, + jsonToWrite.getBytes(Charset.defaultCharset())); + return null; + }) + .when(config) + .save(); + + assertFalse(config.isUpdateAvailable()); + + Updater updater = new Updater(config); + + String validUpdateResponse = + String.format( + "{\n" + + " \"latest\": {\n" + + " \"version\": \"%s\",\n" + + " \"install_unix\": \"curl -L get.web3j.io | sh\",\n" + + " \"install_win\": \"Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/web3j/web3j-installer/master/installer.ps1'))\"\n" + + " }\n" + + "}", + version); + + stubFor( + post(urlPathMatching("/api/versions/latest")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody(validUpdateResponse))); + updater.onlineUpdateCheck(); + + verify(postRequestedFor(urlEqualTo("/api/versions/latest"))); + // if the version parameter does not equal config.getVersion, isUpdateAvailable should + // return true, otherwise it should return false + assertEquals(!version.equals(config.getVersion()), config.isUpdateAvailable()); + + CliConfig realConfigAfterUpdate = CliConfig.getConfig(tempWeb3jSettingsPath.toFile()); + assertEquals( + !version.equals(config.getVersion()), realConfigAfterUpdate.isUpdateAvailable()); + wireMockServer.stop(); + } +}