GitUtil.java

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

Mutations

56

1.1
Location : checkUpdate
Killed by : none
negated conditional → NO_COVERAGE

61

1.1
Location : checkUpdate
Killed by : none
changed conditional boundary → NO_COVERAGE

2.2
Location : checkUpdate
Killed by : none
negated conditional → NO_COVERAGE

63

1.1
Location : checkUpdate
Killed by : none
negated conditional → NO_COVERAGE

110

1.1
Location : sendUnhandledException
Killed by : none
negated conditional → NO_COVERAGE

124

1.1
Location : sendUnhandledException
Killed by : none
removed call to com/jsql/util/GitUtil::sendReport → NO_COVERAGE

135

1.1
Location : sendReport
Killed by : none
negated conditional → NO_COVERAGE

162

1.1
Location : sendReport
Killed by : none
removed call to com/jsql/util/GitUtil::readGithubResponse → NO_COVERAGE

164

1.1
Location : sendReport
Killed by : none
negated conditional → NO_COVERAGE

167

1.1
Location : sendReport
Killed by : none
negated conditional → NO_COVERAGE

168

1.1
Location : sendReport
Killed by : none
removed call to java/lang/Thread::interrupt → NO_COVERAGE

175

1.1
Location : readGithubResponse
Killed by : none
negated conditional → NO_COVERAGE

193

1.1
Location : showNews
Killed by : none
negated conditional → NO_COVERAGE

2.2
Location : showNews
Killed by : none
changed conditional boundary → NO_COVERAGE

206

1.1
Location : callService
Killed by : none
negated conditional → NO_COVERAGE

226

1.1
Location : callService
Killed by : none
replaced return value with null for com/jsql/util/GitUtil::callService → NO_COVERAGE

Active mutators

Tests examined


Report generated by PIT 1.22.0