| 1 | package com.jsql.util; | |
| 2 | ||
| 3 | import com.jsql.model.InjectionModel; | |
| 4 | import com.jsql.model.exception.JSqlRuntimeException; | |
| 5 | import org.apache.commons.lang3.StringUtils; | |
| 6 | import org.apache.commons.lang3.SystemUtils; | |
| 7 | import org.apache.commons.lang3.exception.ExceptionUtils; | |
| 8 | import org.apache.logging.log4j.LogManager; | |
| 9 | import org.apache.logging.log4j.Logger; | |
| 10 | import org.json.JSONException; | |
| 11 | import org.json.JSONObject; | |
| 12 | ||
| 13 | import javax.swing.*; | |
| 14 | import java.awt.*; | |
| 15 | import java.io.IOException; | |
| 16 | import java.net.URI; | |
| 17 | import java.net.http.HttpRequest; | |
| 18 | import java.net.http.HttpRequest.BodyPublishers; | |
| 19 | import java.net.http.HttpResponse; | |
| 20 | import java.net.http.HttpResponse.BodyHandlers; | |
| 21 | import java.time.Duration; | |
| 22 | import java.util.Locale; | |
| 23 | ||
| 24 | /** | |
| 25 | * Utility class used to connect to GitHub Rest webservices. | |
| 26 | * It uses jsql-robot profile to post data to GitHub. | |
| 27 | */ | |
| 28 | public class GitUtil { | |
| 29 | | |
| 30 | private static final Logger LOGGER = LogManager.getRootLogger(); | |
| 31 | | |
| 32 | /** | |
| 33 | * Application useful information as json object from GitHub repository. | |
| 34 | * Used to get current development version and community news. | |
| 35 | */ | |
| 36 | private JSONObject jsonObject; | |
| 37 | | |
| 38 | /** | |
| 39 | * Define explicit labels to declare method parameters. | |
| 40 | * Used for code readability only. | |
| 41 | */ | |
| 42 | public enum ShowOnConsole { YES, NO } | |
| 43 | ||
| 44 | private final InjectionModel injectionModel; | |
| 45 | | |
| 46 | public GitUtil(InjectionModel injectionModel) { | |
| 47 | this.injectionModel = injectionModel; | |
| 48 | } | |
| 49 | ||
| 50 | /** | |
| 51 | * Verify if application is up-to-date against the version on GitHub. | |
| 52 | * @param displayUpdateMessage YES for manual update verification, hidden otherwise | |
| 53 | */ | |
| 54 | public void checkUpdate(ShowOnConsole displayUpdateMessage) { | |
| 55 |
1
1. checkUpdate : negated conditional → NO_COVERAGE |
if (displayUpdateMessage == ShowOnConsole.YES) { |
| 56 | LOGGER.log(LogLevelUtil.CONSOLE_DEFAULT, () -> I18nUtil.valueByKey("UPDATE_LOADING")); | |
| 57 | } | |
| 58 | try { | |
| 59 | var versionGit = Float.parseFloat(this.callService().getString("version")); | |
| 60 |
2
1. checkUpdate : changed conditional boundary → NO_COVERAGE 2. checkUpdate : negated conditional → NO_COVERAGE |
if (versionGit > Float.parseFloat(this.injectionModel.getPropertiesUtil().getVersionJsql())) { |
| 61 | LOGGER.log(LogLevelUtil.CONSOLE_ERROR, () -> I18nUtil.valueByKey("UPDATE_NEW_VERSION")); | |
| 62 |
1
1. checkUpdate : negated conditional → NO_COVERAGE |
} else if (displayUpdateMessage == ShowOnConsole.YES) { |
| 63 | LOGGER.log(LogLevelUtil.CONSOLE_SUCCESS, () -> I18nUtil.valueByKey("UPDATE_UPTODATE")); | |
| 64 | } | |
| 65 | } catch (NumberFormatException | JSONException e) { | |
| 66 | LOGGER.log(LogLevelUtil.CONSOLE_ERROR, I18nUtil.valueByKey("UPDATE_EXCEPTION")); | |
| 67 | } | |
| 68 | } | |
| 69 | ||
| 70 | /** | |
| 71 | * Define the body of an issue to send to GitHub for an unhandled exception. | |
| 72 | * It adds different system data to the body and remove sensible data like | |
| 73 | * injection URL. | |
| 74 | * @param threadName name of thread where the exception occurred | |
| 75 | * @param throwable unhandled exception to report to GitHub | |
| 76 | */ | |
| 77 | public void sendUnhandledException(String threadName, Throwable throwable) { | |
| 78 | Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); | |
| 79 | int width = (int) screenSize.getWidth(); | |
| 80 | int height = (int) screenSize.getHeight(); | |
| 81 | ||
| 82 | var osMetadata = String.join( | |
| 83 | "\n", | |
| 84 | String.format( | |
| 85 | "jSQL: v%s", | |
| 86 | this.injectionModel.getPropertiesUtil().getVersionJsql() | |
| 87 | ), | |
| 88 | String.format( | |
| 89 | "Java: v%s-%s-%s on %s%s", | |
| 90 | SystemUtils.JAVA_VERSION, | |
| 91 | SystemUtils.OS_ARCH, | |
| 92 | SystemUtils.USER_LANGUAGE, | |
| 93 | SystemUtils.JAVA_VENDOR, | |
| 94 | SystemUtils.JAVA_RUNTIME_NAME | |
| 95 | ), | |
| 96 | String.format( | |
| 97 | "OS: %s (v%s)", | |
| 98 | SystemUtils.OS_NAME, SystemUtils.OS_VERSION | |
| 99 | ), | |
| 100 | String.format( | |
| 101 | "Display: %s (%sx%s, %s)", | |
| 102 | UIManager.getLookAndFeel().getName(), | |
| 103 | width, | |
| 104 | height, | |
| 105 | Locale.getDefault().getLanguage() | |
| 106 | ), | |
| 107 | String.format( | |
| 108 | "Strategy: %s", | |
| 109 |
1
1. sendUnhandledException : negated conditional → NO_COVERAGE |
this.injectionModel.getMediatorStrategy().getStrategy() != null |
| 110 | ? this.injectionModel.getMediatorStrategy().getStrategy().getName() | |
| 111 | : "undefined" | |
| 112 | ), | |
| 113 | String.format( | |
| 114 | "Db engine: %s", | |
| 115 | this.injectionModel.getMediatorVendor().getVendor().toString() | |
| 116 | ) | |
| 117 | ); | |
| 118 | | |
| 119 | var exceptionText = String.format("Exception on %s%n%s%n", threadName, ExceptionUtils.getStackTrace(throwable).trim()); | |
| 120 | var clientDescription = String.format("```yaml%n%s%n```%n```java%n%s```", osMetadata, exceptionText); | |
| 121 | | |
| 122 | clientDescription = clientDescription.replaceAll("(https?://[.a-zA-Z_0-9]*)+", StringUtils.EMPTY); | |
| 123 |
1
1. sendUnhandledException : removed call to com/jsql/util/GitUtil::sendReport → NO_COVERAGE |
this.sendReport(clientDescription, ShowOnConsole.NO, "Unhandled "+ throwable.getClass().getSimpleName()); |
| 124 | } | |
| 125 | | |
| 126 | /** | |
| 127 | * Connect to GitHub webservices and create an Issue on the repository. | |
| 128 | * Used by translation protocol, unhandled exception detection and manual Issue reporting. | |
| 129 | * @param reportBody text of the Issue | |
| 130 | * @param showOnConsole in case of manual Issue reporting. Hidden in case of automatic reporting of unhandled exception. | |
| 131 | * @param reportTitle title of the Issue | |
| 132 | */ | |
| 133 | public void sendReport(String reportBody, ShowOnConsole showOnConsole, String reportTitle) { | |
| 134 |
1
1. sendReport : negated conditional → NO_COVERAGE |
if (this.injectionModel.getMediatorUtils().getProxyUtil().isNotLive(showOnConsole)) { |
| 135 | return; | |
| 136 | } | |
| 137 | ||
| 138 | String token; | |
| 139 | try { | |
| 140 | token = StringUtil.fromHexZip( | |
| 141 | this.injectionModel.getMediatorUtils().getPropertiesUtil().getProperty("jsql.hash") | |
| 142 | ); | |
| 143 | } catch (IOException e) { | |
| 144 | throw new JSqlRuntimeException(e); | |
| 145 | } | |
| 146 | ||
| 147 | var httpRequest = HttpRequest.newBuilder() | |
| 148 | .uri(URI.create(this.injectionModel.getMediatorUtils().getPropertiesUtil().getProperty("github.issues.url"))) | |
| 149 | .setHeader("Authorization", "token "+ token) | |
| 150 | .POST(BodyPublishers.ofString( | |
| 151 | new JSONObject() | |
| 152 | .put("title", reportTitle) | |
| 153 | .put("body", reportBody) | |
| 154 | .toString() | |
| 155 | )) | |
| 156 | .timeout(Duration.ofSeconds(15)) | |
| 157 | .build(); | |
| 158 | | |
| 159 | try { | |
| 160 | HttpResponse<String> response = this.injectionModel.getMediatorUtils().getConnectionUtil().getHttpClient().build().send(httpRequest, BodyHandlers.ofString()); | |
| 161 |
1
1. sendReport : removed call to com/jsql/util/GitUtil::readGithubResponse → NO_COVERAGE |
this.readGithubResponse(response, showOnConsole); |
| 162 | } catch (InterruptedException | IOException e) { | |
| 163 |
1
1. sendReport : negated conditional → NO_COVERAGE |
if (showOnConsole == ShowOnConsole.YES) { |
| 164 | LOGGER.log(LogLevelUtil.CONSOLE_ERROR, String.format("Error during GitHub report connection: %s", e.getMessage())); | |
| 165 | } | |
| 166 |
1
1. sendReport : negated conditional → NO_COVERAGE |
if (e instanceof InterruptedException) { |
| 167 |
1
1. sendReport : removed call to java/lang/Thread::interrupt → NO_COVERAGE |
Thread.currentThread().interrupt(); |
| 168 | } | |
| 169 | } | |
| 170 | } | |
| 171 | | |
| 172 | private void readGithubResponse(HttpResponse<String> response, ShowOnConsole showOnConsole) throws IOException { | |
| 173 | try { | |
| 174 |
1
1. readGithubResponse : negated conditional → NO_COVERAGE |
if (showOnConsole == ShowOnConsole.YES) { |
| 175 | var jsonObjectResponse = new JSONObject(response.body()); | |
| 176 | var urlIssue = jsonObjectResponse.getString("html_url"); | |
| 177 | LOGGER.log(LogLevelUtil.CONSOLE_SUCCESS, "Sent to GitHub: {}", urlIssue); | |
| 178 | } | |
| 179 | } catch (Exception e) { | |
| 180 | throw new IOException("Connection to the GitHub API failed, check your connection or update jSQL"); | |
| 181 | } | |
| 182 | } | |
| 183 | | |
| 184 | /** | |
| 185 | * Displays news information on the console from GitHub web service. | |
| 186 | * Infos concern the general roadmap for the application, current development status | |
| 187 | * and other useful statements for the community. | |
| 188 | */ | |
| 189 | public void showNews() { | |
| 190 | try { | |
| 191 | var news = this.callService().getJSONArray("news"); | |
| 192 |
2
1. showNews : negated conditional → NO_COVERAGE 2. showNews : changed conditional boundary → NO_COVERAGE |
for (var index = 0 ; index < news.length() ; index++) { |
| 193 | LOGGER.log(LogLevelUtil.CONSOLE_INFORM, news.get(index)); | |
| 194 | } | |
| 195 | } catch (JSONException e) { | |
| 196 | LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Connection to the GitHub API failed"); | |
| 197 | } | |
| 198 | } | |
| 199 | | |
| 200 | /** | |
| 201 | * Instantiate the jsonObject from json data if not already set. | |
| 202 | * @return jsonObject describing json data | |
| 203 | */ | |
| 204 | public JSONObject callService() { | |
| 205 |
1
1. callService : negated conditional → NO_COVERAGE |
if (this.jsonObject == null) { |
| 206 | String json = this.injectionModel.getMediatorUtils().getConnectionUtil().getSource( | |
| 207 | this.injectionModel.getMediatorUtils().getPropertiesUtil().getProperty("github.webservice.url") | |
| 208 | ); | |
| 209 | // Fix #45349: JSONException on new JSONObject(json) | |
| 210 | try { | |
| 211 | this.jsonObject = new JSONObject(json); | |
| 212 | } catch (JSONException e) { | |
| 213 | try { | |
| 214 | this.jsonObject = new JSONObject("{\"version\": \"0\", \"news\": []}"); | |
| 215 | } catch (JSONException eInner) { | |
| 216 | LOGGER.log(LogLevelUtil.CONSOLE_ERROR, "Fetching default JSON failed", eInner); | |
| 217 | } | |
| 218 | LOGGER.log( | |
| 219 | LogLevelUtil.CONSOLE_ERROR, | |
| 220 | "Fetching configuration from GitHub failed. Wait for service to be available, check your connection or update jSQL" | |
| 221 | ); | |
| 222 | } | |
| 223 | } | |
| 224 |
1
1. callService : replaced return value with null for com/jsql/util/GitUtil::callService → NO_COVERAGE |
return this.jsonObject; |
| 225 | } | |
| 226 | } | |
Mutations | ||
| 55 |
1.1 |
|
| 60 |
1.1 2.2 |
|
| 62 |
1.1 |
|
| 109 |
1.1 |
|
| 123 |
1.1 |
|
| 134 |
1.1 |
|
| 161 |
1.1 |
|
| 163 |
1.1 |
|
| 166 |
1.1 |
|
| 167 |
1.1 |
|
| 174 |
1.1 |
|
| 192 |
1.1 2.2 |
|
| 205 |
1.1 |
|
| 224 |
1.1 |