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

Mutations

58

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

63

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

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

65

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

112

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

126

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

137

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

164

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

166

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

169

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

170

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

177

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

195

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

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

208

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

227

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