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.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
Location : checkUpdate
Killed by : none
negated conditional → NO_COVERAGE

60

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

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

62

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

109

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

123

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

134

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

161

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

163

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

166

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

167

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

174

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

192

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

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

205

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

224

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.19.1