View Javadoc
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          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              if (versionGit > Float.parseFloat(this.injectionModel.getPropertiesUtil().getVersionJsql())) {
62                  LOGGER.log(LogLevelUtil.CONSOLE_ERROR, () -> I18nUtil.valueByKey("UPDATE_NEW_VERSION"));
63              } 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                 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         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         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             this.readGithubResponse(response, showOnConsole);
163         } catch (IOException e) {
164             if (showOnConsole == ShowOnConsole.YES) {
165                 LOGGER.log(LogLevelUtil.CONSOLE_ERROR, String.format("Error during GitHub report connection: %s", e.getMessage()));
166             }
167         } catch (InterruptedException e) {
168             LOGGER.log(LogLevelUtil.IGNORE, e, e);
169             Thread.currentThread().interrupt();
170         }
171     }
172     
173     private void readGithubResponse(HttpResponse<String> response, ShowOnConsole showOnConsole) throws IOException {
174         try {
175             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             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         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         return this.jsonObject;
227     }
228 }