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