diff --git a/build.gradle.kts b/build.gradle.kts index 5d4e059..9f0b07e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,34 +1,47 @@ plugins { id("java") - id("org.jetbrains.intellij") version "1.11.0" + id("org.jetbrains.intellij.platform") version "2.0.1" } group = "com.codealike.client.intellij" - - -version = "1.7.3.0" - - - +version = "1.8.0.1" repositories { mavenCentral() + intellijPlatform{ + defaultRepositories() + } } -val libs: Configuration by configurations.creating - // Configure Gradle IntelliJ Plugin -// Read more: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html -intellij { - - - version.set("2023.3.3") +// Read more: https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-extension.html +intellijPlatform { + + pluginConfiguration{ + ideaVersion { + sinceBuild = "233" + untilBuild = "241.*" + } + } + publishing{ + token = System.getenv("PUBLISH_TOKEN") + } - type.set("IC") // Target IDE Platform + signing{ + certificateChain = System.getenv("CERTIFICATE_CHAIN") + privateKey = System.getenv("PRIVATE_KEY") + password = System.getenv("PRIVATE_KEY_PASSWORD") + } +} - plugins.set(listOf("com.intellij.java")) +dependencies { + intellijPlatform { + create("IC", "2023.3.3") + bundledPlugin("com.intellij.java") + instrumentationTools() + } } tasks { @@ -38,49 +51,7 @@ tasks { targetCompatibility = "17" } - patchPluginXml { - sinceBuild.set("222") - untilBuild.set("233.*") - } - - signPlugin { - certificateChain.set(System.getenv("CERTIFICATE_CHAIN")) - privateKey.set(System.getenv("PRIVATE_KEY")) - password.set(System.getenv("PRIVATE_KEY_PASSWORD")) - } - - publishPlugin { - token.set(System.getenv("PUBLISH_TOKEN")) - } - jar { duplicatesStrategy = DuplicatesStrategy.EXCLUDE - from(libs.map { if (it.isDirectory) it else zipTree(it) }) } } - -dependencies { - libs("jakarta.ws.rs:jakarta.ws.rs-api:3.1.0") - implementation("jakarta.activation:jakarta.activation-api:2.1.0") - implementation("jakarta.annotation:jakarta.annotation-api:2.1.0") - implementation("jakarta.inject:jakarta.inject-api:2.0.1.MR") - libs("jakarta.xml.bind:jakarta.xml.bind-api:4.0.2") - implementation("com.fasterxml.jackson.core:jackson-databind:2.14.0") - implementation("com.fasterxml.jackson.core:jackson-annotations:2.14.0") - implementation("com.fasterxml.jackson.core:jackson-core:2.14.0") - implementation("org.apache.httpcomponents:httpclient:4.5.14") - implementation("org.apache.httpcomponents:httpcore:4.4.16") - implementation("cglib:cglib:3.3.0") - implementation("com.google.guava:guava:31.1-jre") - implementation("org.glassfish.hk2:hk2:3.1.0") - implementation("org.glassfish.hk2:hk2-utils:3.1.0") - implementation("org.glassfish.hk2:hk2-locator:3.1.0") - libs("org.glassfish.jersey.core:jersey-client:3.1.5") - implementation("org.glassfish.jersey.core:jersey-common:3.1.5") - implementation("org.glassfish.jersey.inject:jersey-hk2:3.1.5") - libs("org.osgi:osgi.core:8.0.0") - implementation("log4j:log4j:1.2.17") - libs("joda-time:joda-time:2.12.2") - implementation("nekohtml:nekohtml:1.9.6.2") - configurations.implementation.get().extendsFrom(libs) -} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ae04661..15de902 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/java/com/codealike/client/core/api/ApiClient.java b/src/main/java/com/codealike/client/core/api/ApiClient.java index ffd0c25..681c730 100644 --- a/src/main/java/com/codealike/client/core/api/ApiClient.java +++ b/src/main/java/com/codealike/client/core/api/ApiClient.java @@ -3,26 +3,32 @@ */ package com.codealike.client.core.api; -import com.codealike.client.core.internal.dto.*; +import com.codealike.client.core.internal.dto.ActivityInfo; +import com.codealike.client.core.internal.dto.HealthInfo; +import com.codealike.client.core.internal.dto.PluginSettingsInfo; +import com.codealike.client.core.internal.dto.ProfileInfo; +import com.codealike.client.core.internal.dto.SolutionContextInfo; +import com.codealike.client.core.internal.dto.UserConfigurationInfo; +import com.codealike.client.core.internal.dto.Version; import com.codealike.client.core.internal.startup.PluginContext; +import com.codealike.client.core.internal.utils.LogManager; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; -import jakarta.ws.rs.ProcessingException; -import jakarta.ws.rs.client.*; -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.Response; -import org.glassfish.jersey.client.ClientProperties; +import org.jetbrains.annotations.NotNull; -import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; -import java.net.ConnectException; +import javax.net.ssl.X509TrustManager; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; -import java.security.cert.CertificateException; import java.security.cert.X509Certificate; +import java.time.Duration; import java.util.UUID; /** @@ -32,16 +38,20 @@ * @version 1.6.0.0 */ public class ApiClient { + private static final LogManager LOG = LogManager.INSTANCE; - // Headers for API authentication private static final String X_EAUTH_CLIENT_HEADER = "X-Eauth-Client"; private static final String X_EAUTH_TOKEN_HEADER = "X-Api-Token"; - public static final String X_EAUTH_IDENTITY_HEADER = "X-Api-Identity"; - // Number of API retries - public static final int MAX_RETRIES = 5; - private final WebTarget apiTarget; - private String identity; - private String token; + private static final String X_EAUTH_IDENTITY_HEADER = "X-Api-Identity"; + + private static final int CONNECT_TIMEOUT = 30000; + private static final int READ_TIMEOUT = 5000; + + private final HttpClient httpClient; + private final String apiUrl; + + private final String identity; + private final String token; /** * Create a new API client. Used to communicate with the Codealike remote server. @@ -62,64 +72,80 @@ public static ApiClient tryCreateNew(String identity, String token) throws KeyMa * @throws KeyManagementException if any error with token occurs */ public static ApiClient tryCreateNew() throws KeyManagementException { - return new ApiClient(); + return new ApiClient("", ""); } /** - * API Client constructor. Used to communicate with the Codealike remote server. + * Constructor for `ApiClient` class. + *

+ * It initializes the HttpClient, apiUrl, identity, and token fields. * - * @throws KeyManagementException if any error with token occurs + * @param identity A String representing the identity, if it's null it will be assigned an empty string + * @param token A String representing the token, if it's null it will be assigned an empty string + * @throws KeyManagementException if a failure occurred during the SSL context configuration in the HttpClient */ - protected ApiClient() throws KeyManagementException { - ClientBuilder builder = ClientBuilder.newBuilder(); - TrustManager[] certs = new TrustManager[]{new javax.net.ssl.X509TrustManager() { + private ApiClient(String identity, String token) throws KeyManagementException { + this.httpClient = createHttpClient(); + this.apiUrl = PluginContext.getInstance().getConfiguration().getApiUrl(); + this.identity = identity != null ? identity : ""; + this.token = token != null ? token : ""; + } + + /** + * Factory method that creates and returns an HttpClient object with specified settings. + * + * @return HttpClient which is configured for SSL context, HTTP version, connection timeout, and follow redirects + * @throws KeyManagementException if a failure occurred during retrieving or setting SSL Context or SSL context initialization + */ + private HttpClient createHttpClient() throws KeyManagementException { + TrustManager[] trustManagers = createTrustManagers(); + SSLContext sslContext = createSslContext(trustManagers); + + return HttpClient.newBuilder() + .sslContext(sslContext) + .version(HttpClient.Version.HTTP_2) + .connectTimeout(Duration.ofMillis(CONNECT_TIMEOUT)) + .followRedirects(HttpClient.Redirect.NORMAL) + .build(); + } + + /** + * Factory method that creates and returns an array of TrustManager objects with custom settings. + * + * @return Array of TrustManager objects + */ + private TrustManager[] createTrustManagers() { + return new TrustManager[]{new X509TrustManager() { @Override public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[]{}; + return new X509Certificate[0]; } @Override - public void checkServerTrusted(X509Certificate[] chain, - String authType) throws CertificateException { + public void checkServerTrusted(X509Certificate[] chain, String authType) { } @Override - public void checkClientTrusted(X509Certificate[] chain, - String authType) throws CertificateException { + public void checkClientTrusted(X509Certificate[] chain, String authType) { } }}; - - SSLContext sslContext = null; - try { - sslContext = SSLContext.getInstance("SSL"); - sslContext.init(null, certs, new SecureRandom()); - } catch (NoSuchAlgorithmException e1) { - e1.printStackTrace(); - } - - builder.sslContext(sslContext).hostnameVerifier(HttpsURLConnection.getDefaultHostnameVerifier()); - - Client client = builder.build(); - client.property(ClientProperties.CONNECT_TIMEOUT, 30000); - client.property(ClientProperties.READ_TIMEOUT, 5000); - - apiTarget = client.target(PluginContext.getInstance().getConfiguration().getApiUrl()); - this.identity = ""; - this.token = ""; } /** - * API Client constructor. + * Factory method that creates and returns an SSLContext object with specified TrustManagers. * - * @param identity the user identity - * @param token the user token - * @throws KeyManagementException if any error with token occurs + * @param trustManagers an array of trust managers to use for initializing the SSL context + * @return SSLContext which is configured with the given trust managers + * @throws KeyManagementException if a failure occurred during initializing the SSL context */ - protected ApiClient(String identity, String token) throws KeyManagementException { - this(); - if (identity != null && token != null) { - this.identity = identity; - this.token = token; + private SSLContext createSslContext(TrustManager[] trustManagers) throws KeyManagementException { + try { + SSLContext sslContext = SSLContext.getInstance("SSL"); + sslContext.init(null, trustManagers, new SecureRandom()); + return sslContext; + } catch (NoSuchAlgorithmException | KeyManagementException exception) { + LOG.logWarn("Error initializing SSL context: " + exception.getMessage()); + throw new KeyManagementException("Failed to initialize SSL context", exception); } } @@ -130,105 +156,103 @@ protected ApiClient(String identity, String token) throws KeyManagementException */ public static ApiResponse getPluginSettings() { ObjectMapper mapper = new ObjectMapper(); - ClientBuilder builder = ClientBuilder.newBuilder(); - Client client = builder.build(); - client.property(ClientProperties.CONNECT_TIMEOUT, 30000); - client.property(ClientProperties.READ_TIMEOUT, 5000); + HttpClient client = HttpClient.newBuilder() + .connectTimeout(Duration.ofMillis(CONNECT_TIMEOUT)) + .build(); - WebTarget pluginSettingsTarget = client.target("https://codealike.com/api/v2/public/PluginsConfiguration"); - - Invocation.Builder invocationBuilder = pluginSettingsTarget.request( - MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("https://codealike.com/api/v2/public/PluginsConfiguration")) + .header("Accept", "application/json") + .timeout(Duration.ofMillis(READ_TIMEOUT)) + .GET() + .build(); try { - Response response; - try { - response = invocationBuilder.get(); - } catch (Exception e) { - return new ApiResponse<>(ApiResponse.Status.ConnectionProblems); - } - - if (response.getStatusInfo().getStatusCode() == Response.Status.OK.getStatusCode()) { - // process response to get a valid json string representation - String serializedObject = response.readEntity(String.class); + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() == 200) { + String serializedObject = response.body(); String normalizedObject = serializedObject.substring(1, serializedObject.length() - 1).replace("\\", ""); - // parse the json object to get a valid plugin settings object PluginSettingsInfo pluginSettingsInfo = mapper.readValue(normalizedObject, PluginSettingsInfo.class); - if (pluginSettingsInfo != null) { - return new ApiResponse<>( - response.getStatus(), response.getStatusInfo() - .getReasonPhrase(), pluginSettingsInfo); + return new ApiResponse<>(response.statusCode(), response.headers() + .firstValue("reason").orElse("OK"), pluginSettingsInfo); } else { return new ApiResponse<>(ApiResponse.Status.ClientError, "Problem parsing data from the server."); } } else { - return new ApiResponse<>(response.getStatus(), response - .getStatusInfo().getReasonPhrase()); + return new ApiResponse<>(response.statusCode(), response.headers() + .firstValue("reason").orElse("Error")); } - } catch (Exception e) { + } catch (Exception exception) { return new ApiResponse<>(ApiResponse.Status.ClientError, - String.format("Problem parsing data from the server. %s", - e.getMessage())); + String.format("Problem parsing data from the server: %s", exception.getMessage())); } } /** - * Check API health. + * Log the plugin health information to the remote server. * - * @return the {@link ApiResponse} instance + * @param healthInfo the health information object to update */ - public ApiResponse health() { + public void logHealth(HealthInfo healthInfo) { try { - WebTarget target = apiTarget.path("health"); - - Invocation.Builder invocationBuilder = target - .request(MediaType.APPLICATION_JSON); - Response response = invocationBuilder.get(); - return new ApiResponse<>(response.getStatus(), response - .getStatusInfo().getReasonPhrase()); - } catch (ProcessingException e) { - if (e.getCause() != null - && e.getCause() instanceof ConnectException) { - return new ApiResponse<>(ApiResponse.Status.ConnectionProblems); - } else { - return new ApiResponse<>(ApiResponse.Status.ClientError); - } + URI uri = URI.create(apiUrl + "/health"); + + String healthInfoLog = serializeDataToJson(healthInfo); + + HttpRequest request = HttpRequest.newBuilder() + .uri(uri) + .header("Accept", "application/json") + .header("Content-Type", "application/json") + .header(X_EAUTH_IDENTITY_HEADER, this.identity) + .header(X_EAUTH_TOKEN_HEADER, this.token) + .header(X_EAUTH_CLIENT_HEADER, "intellij") + .timeout(Duration.ofMillis(READ_TIMEOUT)) + .PUT(HttpRequest.BodyPublishers.ofString(healthInfoLog)) + .build(); + + HttpClient client = HttpClient.newHttpClient(); + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.discarding()); + + int statusCode = response.statusCode(); + String reasonPhrase = response.headers().firstValue("reason").orElse("No reason provided"); + + LOG.logInfo("Health log response: " + statusCode + " - " + reasonPhrase); + + } catch (JsonProcessingException exception) { + LOG.logWarn("Error processing JSON for health log: " + exception.getMessage()); + } catch (Exception exception) { + LOG.logWarn("Error sending health log request: " + exception.getMessage()); } } /** - * Log the plugin health information to the remote server. + * Do an account authentication using the Codealike token. * - * @param healthInfo the health information object to update * @return the {@link ApiResponse} instance */ - public ApiResponse logHealth(HealthInfo healthInfo) { + public ApiResponse tokenAuthenticate() { try { - WebTarget target = apiTarget.path("health"); - - ObjectWriter writer = PluginContext.getInstance().getJsonWriter(); - String healthInfoLog = writer.writeValueAsString(healthInfo); - - Invocation.Builder invocationBuilder = target.request().accept( - MediaType.APPLICATION_JSON); - addHeaders(invocationBuilder); - - Response response; - try { - response = invocationBuilder.put(Entity.entity(healthInfoLog, - MediaType.APPLICATION_JSON)); - } catch (Exception e) { - return new ApiResponse<>(ApiResponse.Status.ConnectionProblems); - } - return new ApiResponse<>(response.getStatus(), response - .getStatusInfo().getReasonPhrase()); - } catch (JsonProcessingException e) { - return new ApiResponse<>(ApiResponse.Status.ClientError, - String.format("Problem parsing data from the server. %s", - e.getMessage())); + URI uri = URI.create(apiUrl + "/account/" + this.identity + "/authorized"); + + HttpRequest request = HttpRequest.newBuilder() + .uri(uri) + .header("Accept", "application/json") + .header(X_EAUTH_IDENTITY_HEADER, this.identity) + .header(X_EAUTH_TOKEN_HEADER, this.token) + .header(X_EAUTH_CLIENT_HEADER, "intellij") + .timeout(Duration.ofMillis(READ_TIMEOUT)) + .GET() + .build(); + + HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.discarding()); + + return new ApiResponse<>(response.statusCode(), response.headers().firstValue("reason").orElse("OK")); + } catch (Exception exception) { + LOG.logWarn("Error during token authentication " + exception.getMessage()); + return new ApiResponse<>(ApiResponse.Status.ConnectionProblems); } } @@ -238,8 +262,8 @@ public ApiResponse logHealth(HealthInfo healthInfo) { * @return the {@link ApiResponse} instance with {@link Version} information */ public ApiResponse version() { - WebTarget target = apiTarget.path("version").queryParam("client", "intellij"); - return doGet(target, Version.class); + String path = "/version?client=intellij"; + return get(path, Version.class); } /** @@ -249,8 +273,8 @@ public ApiResponse version() { * @return the {@link ApiResponse} instance with {@link SolutionContextInfo} information */ public ApiResponse getSolutionContext(UUID projectId) { - WebTarget target = apiTarget.path("solution").path(projectId.toString()); - return doGet(target, SolutionContextInfo.class); + String path = "/solution/" + projectId.toString(); + return get(path, SolutionContextInfo.class); } /** @@ -260,8 +284,8 @@ public ApiResponse getSolutionContext(UUID projectId) { * @return the {@link ApiResponse} instance with {@link ProfileInfo} information */ public ApiResponse getProfile(String username) { - WebTarget target = apiTarget.path("account").path(username).path("profile"); - return doGet(target, ProfileInfo.class); + String path = "/account/" + username + "/profile"; + return get(path, ProfileInfo.class); } /** @@ -271,8 +295,8 @@ public ApiResponse getProfile(String username) { * @return the {@link ApiResponse} instance with {@link UserConfigurationInfo} information */ public ApiResponse getUserConfiguration(String username) { - WebTarget target = apiTarget.path("account").path(username).path("config"); - return doGet(target, UserConfigurationInfo.class); + String path = "/account/" + username + "/config"; + return get(path, UserConfigurationInfo.class); } /** @@ -283,31 +307,8 @@ public ApiResponse getUserConfiguration(String username) * @return the {@link ApiResponse} instance */ public ApiResponse registerProjectContext(UUID projectId, String name) { - try { - SolutionContextInfo solutionContext = new SolutionContextInfo( - projectId, name); - WebTarget target = apiTarget.path("solution"); - - ObjectWriter writer = PluginContext.getInstance().getJsonWriter(); - String solutionAsJson = writer.writeValueAsString(solutionContext); - Invocation.Builder invocationBuilder = target.request().accept( - MediaType.APPLICATION_JSON); - addHeaders(invocationBuilder); - - Response response; - try { - response = invocationBuilder.post(Entity.entity(solutionAsJson, - MediaType.APPLICATION_JSON)); - } catch (Exception e) { - return new ApiResponse<>(ApiResponse.Status.ConnectionProblems); - } - return new ApiResponse<>(response.getStatus(), response - .getStatusInfo().getReasonPhrase()); - } catch (JsonProcessingException e) { - return new ApiResponse<>(ApiResponse.Status.ClientError, - String.format("Problem parsing data from the server. %s", - e.getMessage())); - } + String path = "/solution"; + return post(path, new SolutionContextInfo(projectId, name)); } /** @@ -317,104 +318,90 @@ public ApiResponse registerProjectContext(UUID projectId, String name) { * @return the {@link ApiResponse} instance */ public ApiResponse postActivityInfo(ActivityInfo info) { - try { - WebTarget target = apiTarget.path("activity"); - - ObjectWriter writer = PluginContext.getInstance().getJsonWriter(); - String activityInfoAsJson = writer.writeValueAsString(info); - Invocation.Builder invocationBuilder = target.request().accept( - MediaType.APPLICATION_JSON); - addHeaders(invocationBuilder); - - Response response; - try { - response = invocationBuilder.post(Entity.entity( - activityInfoAsJson, MediaType.APPLICATION_JSON)); - } catch (Exception e) { - return new ApiResponse<>(ApiResponse.Status.ConnectionProblems); - } - return new ApiResponse<>(response.getStatus(), response - .getStatusInfo().getReasonPhrase()); - } catch (JsonProcessingException e) { - return new ApiResponse<>(ApiResponse.Status.ClientError, - String.format("Problem parsing data from the server. %s", - e.getMessage())); - } + String path = "/activity"; + return post(path, info); } /** - * Do an account authentication using the Codealike token. - * - * @return the {@link ApiResponse} instance + * Private method to do an API POST. */ - public ApiResponse tokenAuthenticate() { - WebTarget target = apiTarget.path("account").path(this.identity) - .path("authorized"); - Invocation.Builder invocationBuilder = target.request( - MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON); - - addHeaders(invocationBuilder); - - Response response; + @NotNull + private ApiResponse post(String path, Object data) { try { - response = invocationBuilder.get(); + URI uri = URI.create(apiUrl + path); + String jsonData = serializeDataToJson(data); + + HttpRequest request = HttpRequest.newBuilder() + .uri(uri) + .header("Accept", "application/json") + .header("Content-Type", "application/json") + .header(X_EAUTH_IDENTITY_HEADER, this.identity) + .header(X_EAUTH_TOKEN_HEADER, this.token) + .header(X_EAUTH_CLIENT_HEADER, "intellij") + .timeout(Duration.ofMillis(READ_TIMEOUT)) + .POST(HttpRequest.BodyPublishers.ofString(jsonData)) + .build(); + HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.discarding()); + + return new ApiResponse<>(response.statusCode(), + response.headers().firstValue("reason").orElse("OK")); + } catch (JsonProcessingException e) { + return new ApiResponse<>(ApiResponse.Status.ClientError, + String.format("Problem parsing data to JSON: %s", e.getMessage())); } catch (Exception e) { return new ApiResponse<>(ApiResponse.Status.ConnectionProblems); } - return new ApiResponse<>(response.getStatus(), response.getStatusInfo() - .getReasonPhrase()); - } - - /** - * Private method to add headers to request. - */ - private void addHeaders(Invocation.Builder invocationBuilder) { - invocationBuilder.header(X_EAUTH_IDENTITY_HEADER, this.identity); - invocationBuilder.header(X_EAUTH_TOKEN_HEADER, this.token); - invocationBuilder.header(X_EAUTH_CLIENT_HEADER, "intellij"); } /** * Private method to do an API GET. */ - private ApiResponse doGet(WebTarget target, Class type) { - Invocation.Builder invocationBuilder = target.request( - MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON); - addHeaders(invocationBuilder); - + private ApiResponse get(String path, Class type) { try { - Response response; - try { - response = invocationBuilder.get(); - } catch (Exception e) { - return new ApiResponse<>(ApiResponse.Status.ConnectionProblems); - } - - if (response.getStatusInfo().getStatusCode() == Response.Status.OK - .getStatusCode()) { - String solutionContextInfoSerialized = response - .readEntity(String.class); - ObjectMapper mapper = PluginContext.getInstance() - .getJsonMapper(); - T contextInfo = mapper.readValue( - solutionContextInfoSerialized, - type); + URI uri = URI.create(apiUrl + path); + + HttpRequest request = HttpRequest.newBuilder() + .uri(uri) + .header("Accept", "application/json") + .header("Content-Type", "application/json") + .header(X_EAUTH_IDENTITY_HEADER, this.identity) + .header(X_EAUTH_TOKEN_HEADER, this.token) + .header(X_EAUTH_CLIENT_HEADER, "intellij") + .timeout(Duration.ofMillis(READ_TIMEOUT)) + .GET() + .build(); + + HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + + if (response.statusCode() == 200) { + ObjectMapper mapper = PluginContext.getInstance().getJsonMapper(); + T contextInfo = mapper.readValue(response.body(), type); if (contextInfo != null) { - return new ApiResponse<>( - response.getStatus(), response.getStatusInfo() - .getReasonPhrase(), contextInfo); + return new ApiResponse<>(response.statusCode(), + response.headers().firstValue("reason").orElse("OK"), contextInfo); } else { return new ApiResponse<>(ApiResponse.Status.ClientError, "Problem parsing data from the server."); } } else { - return new ApiResponse<>(response.getStatus(), response - .getStatusInfo().getReasonPhrase()); + return new ApiResponse<>(response.statusCode(), + response.headers().firstValue("reason").orElse("Error")); } - } catch (Exception e) { + } catch (Exception exception) { return new ApiResponse<>(ApiResponse.Status.ClientError, - String.format("Problem parsing data from the server. %s", - e.getMessage())); + String.format("Problem parsing data from the server: %s", exception.getMessage())); } } + + /** + * Converts a given object to its JSON representation. + * + * @param data the object that needs to be converted to JSON. + * @return a String containing the JSON representation of the input object. + * @throws JsonProcessingException if the input object could not be converted to JSON. + */ + private String serializeDataToJson(Object data) throws JsonProcessingException { + ObjectWriter writer = PluginContext.getInstance().getJsonWriter(); + return writer.writeValueAsString(data); + } } diff --git a/src/main/java/com/codealike/client/core/internal/dto/ActivityEntryInfo.java b/src/main/java/com/codealike/client/core/internal/dto/ActivityEntryInfo.java index 2c64048..4225ff8 100644 --- a/src/main/java/com/codealike/client/core/internal/dto/ActivityEntryInfo.java +++ b/src/main/java/com/codealike/client/core/internal/dto/ActivityEntryInfo.java @@ -1,22 +1,18 @@ package com.codealike.client.core.internal.dto; -import org.joda.time.DateTime; -import org.joda.time.Period; - +import java.time.Duration; +import java.time.OffsetDateTime; import java.util.UUID; public class ActivityEntryInfo { private UUID parentId; - private DateTime start; - private DateTime end; + private OffsetDateTime start; + private OffsetDateTime end; private ActivityType type; - private Period duration; + private Duration duration; private CodeContextInfo context; - public ActivityEntryInfo() { - } - public ActivityEntryInfo(UUID parentId) { this.parentId = parentId; } @@ -45,27 +41,27 @@ public void setType(ActivityType type) { this.type = type; } - public DateTime getStart() { + public OffsetDateTime getStart() { return start; } - public void setStart(DateTime start) { + public void setStart(OffsetDateTime start) { this.start = start; } - public DateTime getEnd() { + public OffsetDateTime getEnd() { return end; } - public void setEnd(DateTime end) { + public void setEnd(OffsetDateTime end) { this.end = end; } - public Period getDuration() { + public Duration getDuration() { return duration; } - public void setDuration(Period duration) { + public void setDuration(Duration duration) { this.duration = duration; } diff --git a/src/main/java/com/codealike/client/core/internal/dto/ActivityInfo.java b/src/main/java/com/codealike/client/core/internal/dto/ActivityInfo.java index b927f6c..47dfeee 100644 --- a/src/main/java/com/codealike/client/core/internal/dto/ActivityInfo.java +++ b/src/main/java/com/codealike/client/core/internal/dto/ActivityInfo.java @@ -1,8 +1,8 @@ package com.codealike.client.core.internal.dto; import com.fasterxml.jackson.annotation.JsonIgnore; -import org.joda.time.DateTime; +import java.time.OffsetDateTime; import java.util.List; import java.util.UUID; @@ -17,13 +17,10 @@ public class ActivityInfo { private String instance; private UUID solutionId; private UUID batchId; - private DateTime batchStart; - private DateTime batchEnd; + private OffsetDateTime batchStart; + private OffsetDateTime batchEnd; - public ActivityInfo() { - } - - public ActivityInfo(String instance, UUID solutionId, UUID batchId, DateTime batchStart, DateTime batchEnd) { + public ActivityInfo(String instance, UUID solutionId, UUID batchId, OffsetDateTime batchStart, OffsetDateTime batchEnd) { this.instance = instance; this.solutionId = solutionId; this.batchId = batchId; @@ -109,25 +106,22 @@ public boolean isValid() { // if (this.projects == null || this.projects.isEmpty()) { // return false; // } - if (this.states == null || this.states.isEmpty()) { - return false; - } - return true; + return this.states != null && !this.states.isEmpty(); } - public DateTime getBatchStart() { + public OffsetDateTime getBatchStart() { return batchStart; } - public void setBatchStart(DateTime batchStart) { + public void setBatchStart(OffsetDateTime batchStart) { this.batchStart = batchStart; } - public DateTime getBatchEnd() { + public OffsetDateTime getBatchEnd() { return batchEnd; } - public void setBatchEnd(DateTime batchEnd) { + public void setBatchEnd(OffsetDateTime batchEnd) { this.batchEnd = batchEnd; } } diff --git a/src/main/java/com/codealike/client/core/internal/dto/HealthInfo.java b/src/main/java/com/codealike/client/core/internal/dto/HealthInfo.java index 0dc90af..55b14d5 100644 --- a/src/main/java/com/codealike/client/core/internal/dto/HealthInfo.java +++ b/src/main/java/com/codealike/client/core/internal/dto/HealthInfo.java @@ -4,10 +4,10 @@ public class HealthInfo { - private String identity; - private String source; - private String message; - private HealthInfoType type; + private final String identity; + private final String source; + private final String message; + private final HealthInfoType type; public HealthInfo(Exception ex, String message, String source, HealthInfoType type, String identity) { this.identity = identity; @@ -16,13 +16,6 @@ public HealthInfo(Exception ex, String message, String source, HealthInfoType ty this.source = source; } - public HealthInfo(String message, String source, HealthInfoType type, String identity) { - this.identity = identity; - this.message = message; - this.type = type; - this.source = source; - } - public String getIdentity() { return identity; } diff --git a/src/main/java/com/codealike/client/core/internal/dto/SolutionContextInfo.java b/src/main/java/com/codealike/client/core/internal/dto/SolutionContextInfo.java index 08e328c..9bf8139 100644 --- a/src/main/java/com/codealike/client/core/internal/dto/SolutionContextInfo.java +++ b/src/main/java/com/codealike/client/core/internal/dto/SolutionContextInfo.java @@ -1,8 +1,8 @@ package com.codealike.client.core.internal.dto; import com.fasterxml.jackson.annotation.JsonProperty; -import org.joda.time.DateTime; +import java.time.OffsetDateTime; import java.util.UUID; public class SolutionContextInfo { @@ -12,16 +12,12 @@ public class SolutionContextInfo { @JsonProperty("Name") private String name; @JsonProperty("CreationTime") - private DateTime creationTime; - - public SolutionContextInfo() { - this.creationTime = new DateTime(0); - } + private OffsetDateTime creationTime; public SolutionContextInfo(UUID solutionID, String name) { this.solutionId = solutionID; this.name = name; - this.creationTime = DateTime.now(); + this.creationTime = OffsetDateTime.now(); } public UUID getSolutionId() { @@ -42,12 +38,12 @@ public void setName(String name) { this.name = name; } - public DateTime getCreationTime() { + public OffsetDateTime getCreationTime() { return creationTime; } @JsonProperty("CreationTime") - public void setCreationTime(DateTime creationTime) { + public void setCreationTime(OffsetDateTime creationTime) { this.creationTime = creationTime; } } diff --git a/src/main/java/com/codealike/client/core/internal/model/ActivityEvent.java b/src/main/java/com/codealike/client/core/internal/model/ActivityEvent.java index fa70ada..1b6b235 100644 --- a/src/main/java/com/codealike/client/core/internal/model/ActivityEvent.java +++ b/src/main/java/com/codealike/client/core/internal/model/ActivityEvent.java @@ -4,9 +4,9 @@ package com.codealike.client.core.internal.model; import com.codealike.client.core.internal.dto.ActivityType; -import org.joda.time.DateTime; -import org.joda.time.Period; +import java.time.Duration; +import java.time.OffsetDateTime; import java.util.UUID; /** @@ -19,14 +19,14 @@ public class ActivityEvent implements IEndable { protected ActivityType type; protected CodeContext context; - protected DateTime creationTime; - protected Period duration; + protected OffsetDateTime creationTime; + protected Duration duration; protected UUID projectId; public ActivityEvent(UUID projectId, ActivityType type, CodeContext context) { - creationTime = DateTime.now(); - duration = Period.ZERO; + creationTime = OffsetDateTime.now(); + duration = Duration.ZERO; this.type = type; this.context = context; this.projectId = projectId; @@ -44,19 +44,19 @@ public void setContext(CodeContext context) { this.context = context; } - public DateTime getCreationTime() { + public OffsetDateTime getCreationTime() { return creationTime; } - public void setCreationTime(DateTime creationTime) { + public void setCreationTime(OffsetDateTime creationTime) { this.creationTime = creationTime; } - public Period getDuration() { + public Duration getDuration() { return duration; } - public void setDuration(Period duration) { + public void setDuration(Duration duration) { this.duration = duration; } @@ -81,8 +81,8 @@ public ActivityEvent recreate() { return new ActivityEvent(this.projectId, this.type, this.getContext()); } - public void closeDuration(DateTime closeTo) { - this.duration = new Period(this.getCreationTime(), closeTo); + public void closeDuration(OffsetDateTime closeTo) { + this.duration = Duration.between(this.getCreationTime(), closeTo); } public boolean isEquivalent(ActivityEvent event) { @@ -95,8 +95,7 @@ public boolean isEquivalent(ActivityEvent event) { public boolean equals(Object event) { if (event == null) return false; if (event == this) return true; - if (!(event instanceof ActivityEvent)) return false; - ActivityEvent eventClass = (ActivityEvent) event; + if (!(event instanceof ActivityEvent eventClass)) return false; return (this.getProjectId() == eventClass.getProjectId() && this.getType() == eventClass.getType() diff --git a/src/main/java/com/codealike/client/core/internal/model/ActivityState.java b/src/main/java/com/codealike/client/core/internal/model/ActivityState.java index 175a50d..fd5dc51 100644 --- a/src/main/java/com/codealike/client/core/internal/model/ActivityState.java +++ b/src/main/java/com/codealike/client/core/internal/model/ActivityState.java @@ -5,9 +5,9 @@ import com.codealike.client.core.internal.dto.ActivityType; import com.codealike.client.core.internal.startup.PluginContext; -import org.joda.time.DateTime; -import org.joda.time.Period; +import java.time.Duration; +import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -22,37 +22,37 @@ public class ActivityState implements IEndable { public static final ActivityState NONE = new ActivityState(); - protected Period duration; + protected Duration duration; protected ActivityType type; - protected DateTime creationTime; + protected OffsetDateTime creationTime; protected UUID projectId; protected ActivityState() { this.type = ActivityType.None; - this.duration = Period.ZERO; + this.duration = Duration.ZERO; } - protected ActivityState(UUID projectId, ActivityType type, DateTime creationTime) { + protected ActivityState(UUID projectId, ActivityType type, OffsetDateTime creationTime) { this.projectId = projectId; this.creationTime = creationTime; this.type = type; - this.duration = Period.ZERO; + this.duration = Duration.ZERO; } public static ActivityState createDebugState(UUID projectId) { - return new ActivityState(projectId, ActivityType.Debugging, DateTime.now()); + return new ActivityState(projectId, ActivityType.Debugging, OffsetDateTime.now()); } public static ActivityState createDesignState(UUID projectId) { - return new ActivityState(projectId, ActivityType.Coding, DateTime.now()); + return new ActivityState(projectId, ActivityType.Coding, OffsetDateTime.now()); } public static ActivityState createBuildState(UUID projectId) { - return new ActivityState(projectId, ActivityType.Building, DateTime.now()); + return new ActivityState(projectId, ActivityType.Building, OffsetDateTime.now()); } public static ActivityState createSystemState(UUID projectId) { - return new ActivityState(projectId, ActivityType.System, DateTime.now()); + return new ActivityState(projectId, ActivityType.System, OffsetDateTime.now()); } public static IdleActivityState createIdleState(UUID projectId) { @@ -71,11 +71,11 @@ public static ActivityState createNullState(UUID projectId) { return NullActivityState.createNew(projectId); } - public Period getDuration() { + public Duration getDuration() { return duration; } - public void setDuration(Period duration) { + public void setDuration(Duration duration) { this.duration = duration; } @@ -83,20 +83,20 @@ public ActivityType getType() { return type; } - public DateTime getCreationTime() { + public OffsetDateTime getCreationTime() { return creationTime; } - public void setCreationTime(DateTime startWorkspaceDate) { + public void setCreationTime(OffsetDateTime startWorkspaceDate) { this.creationTime = startWorkspaceDate; } public ActivityState recreate() { - return new ActivityState(this.projectId, this.type, DateTime.now()); + return new ActivityState(this.projectId, this.type, OffsetDateTime.now()); } - public void closeDuration(DateTime closeTo) { - this.duration = new Period(this.getCreationTime(), closeTo); + public void closeDuration(OffsetDateTime closeTo) { + this.duration = Duration.between(this.getCreationTime(), closeTo); } public UUID getProjectId() { @@ -117,8 +117,7 @@ public boolean canShrink() { public boolean equals(Object state) { if (state == null) return false; if (state == this) return true; - if (!(state instanceof ActivityState)) return false; - ActivityState stateClass = (ActivityState) state; + if (!(state instanceof ActivityState stateClass)) return false; return (this.getProjectId() == stateClass.getProjectId() && this.getType() == stateClass.getType()); diff --git a/src/main/java/com/codealike/client/core/internal/model/IEndable.java b/src/main/java/com/codealike/client/core/internal/model/IEndable.java index d0b11a6..5cf2d28 100644 --- a/src/main/java/com/codealike/client/core/internal/model/IEndable.java +++ b/src/main/java/com/codealike/client/core/internal/model/IEndable.java @@ -4,8 +4,9 @@ package com.codealike.client.core.internal.model; import com.codealike.client.core.internal.dto.ActivityType; -import org.joda.time.DateTime; -import org.joda.time.Period; + +import java.time.Duration; +import java.time.OffsetDateTime; /** * Endable model interface. @@ -14,11 +15,11 @@ * @version 1.6.0.0 */ public interface IEndable { - DateTime getCreationTime(); + OffsetDateTime getCreationTime(); - Period getDuration(); + Duration getDuration(); - void setDuration(Period duration); + void setDuration(Duration duration); ActivityType getType(); } diff --git a/src/main/java/com/codealike/client/core/internal/model/IdleActivityState.java b/src/main/java/com/codealike/client/core/internal/model/IdleActivityState.java index 8e99fb0..0c3bd9b 100644 --- a/src/main/java/com/codealike/client/core/internal/model/IdleActivityState.java +++ b/src/main/java/com/codealike/client/core/internal/model/IdleActivityState.java @@ -4,8 +4,8 @@ package com.codealike.client.core.internal.model; import com.codealike.client.core.internal.dto.ActivityType; -import org.joda.time.DateTime; +import java.time.OffsetDateTime; import java.util.UUID; /** @@ -16,30 +16,30 @@ */ public class IdleActivityState extends ActivityState { - private DateTime lastActivity; + private OffsetDateTime lastActivity; - public IdleActivityState(UUID projectId, ActivityType type, DateTime creationTime) { + public IdleActivityState(UUID projectId, ActivityType type, OffsetDateTime creationTime) { super(projectId, type, creationTime); } protected static IdleActivityState createNew(UUID projectId) { - IdleActivityState state = new IdleActivityState(projectId, ActivityType.Idle, DateTime.now()); + IdleActivityState state = new IdleActivityState(projectId, ActivityType.Idle, OffsetDateTime.now()); state.lastActivity = state.getCreationTime(); return state; } - public DateTime getLastActivity() { + public OffsetDateTime getLastActivity() { return lastActivity; } - public void setLastActivity(DateTime lastActivity) { + public void setLastActivity(OffsetDateTime lastActivity) { this.lastActivity = lastActivity; } @Override public IdleActivityState recreate() { - return new IdleActivityState(this.projectId, this.type, DateTime.now()); + return new IdleActivityState(this.projectId, this.type, OffsetDateTime.now()); } } diff --git a/src/main/java/com/codealike/client/core/internal/model/NullActivityState.java b/src/main/java/com/codealike/client/core/internal/model/NullActivityState.java index 6fb8dec..3e9d0a8 100644 --- a/src/main/java/com/codealike/client/core/internal/model/NullActivityState.java +++ b/src/main/java/com/codealike/client/core/internal/model/NullActivityState.java @@ -5,8 +5,8 @@ import com.codealike.client.core.internal.dto.ActivityType; import com.codealike.client.core.internal.startup.PluginContext; -import org.joda.time.DateTime; +import java.time.OffsetDateTime; import java.util.UUID; /** @@ -17,25 +17,21 @@ */ public class NullActivityState extends ActivityState { - public NullActivityState(ActivityType type, DateTime creationTime, UUID projectId) { + public NullActivityState(ActivityType type, OffsetDateTime creationTime, UUID projectId) { super(projectId, type, creationTime); } protected static NullActivityState createNew() { - NullActivityState state = new NullActivityState(ActivityType.Idle, DateTime.now(), PluginContext.UNASSIGNED_PROJECT); - - return state; + return new NullActivityState(ActivityType.Idle, OffsetDateTime.now(), PluginContext.UNASSIGNED_PROJECT); } protected static NullActivityState createNew(UUID projectId) { - NullActivityState state = new NullActivityState(ActivityType.Idle, DateTime.now(), projectId); - - return state; + return new NullActivityState(ActivityType.Idle, OffsetDateTime.now(), projectId); } @Override public NullActivityState recreate() { - return new NullActivityState(this.type, DateTime.now(), this.projectId); + return new NullActivityState(this.type, OffsetDateTime.now(), this.projectId); } } diff --git a/src/main/java/com/codealike/client/core/internal/model/exception/IncompatibleVersionException.java b/src/main/java/com/codealike/client/core/internal/model/exception/IncompatibleVersionException.java new file mode 100644 index 0000000..b72cc6e --- /dev/null +++ b/src/main/java/com/codealike/client/core/internal/model/exception/IncompatibleVersionException.java @@ -0,0 +1,7 @@ +package com.codealike.client.core.internal.model.exception; + +public class IncompatibleVersionException extends Exception { + public IncompatibleVersionException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/src/main/java/com/codealike/client/core/internal/processing/ActivityInfoProcessor.java b/src/main/java/com/codealike/client/core/internal/processing/ActivityInfoProcessor.java index 04c10a1..61b60cf 100644 --- a/src/main/java/com/codealike/client/core/internal/processing/ActivityInfoProcessor.java +++ b/src/main/java/com/codealike/client/core/internal/processing/ActivityInfoProcessor.java @@ -10,8 +10,8 @@ import com.codealike.client.core.internal.model.ActivityEvent; import com.codealike.client.core.internal.model.ActivityState; import com.codealike.client.core.internal.startup.PluginContext; -import org.joda.time.DateTime; +import java.time.OffsetDateTime; import java.util.LinkedList; import java.util.List; import java.util.UUID; @@ -27,8 +27,8 @@ public class ActivityInfoProcessor { private final List processedStates; // list of processed events private final List processedEvents; - private DateTime batchStart; - private DateTime batchEnd; + private final OffsetDateTime batchStart; + private final OffsetDateTime batchEnd; /** * Activity information processor constructor @@ -36,7 +36,7 @@ public class ActivityInfoProcessor { * @param states the states to process * @param events the events to process */ - public ActivityInfoProcessor(List states, List events, DateTime batchStart, DateTime batchEnd) { + public ActivityInfoProcessor(List states, List events, OffsetDateTime batchStart, OffsetDateTime batchEnd) { this.processedStates = states; this.processedEvents = events; this.batchStart = batchStart; diff --git a/src/main/java/com/codealike/client/core/internal/serialization/ActivityTypeDeserializer.java b/src/main/java/com/codealike/client/core/internal/serialization/ActivityTypeDeserializer.java index 7bc8748..7b97ccd 100644 --- a/src/main/java/com/codealike/client/core/internal/serialization/ActivityTypeDeserializer.java +++ b/src/main/java/com/codealike/client/core/internal/serialization/ActivityTypeDeserializer.java @@ -5,7 +5,6 @@ import com.codealike.client.core.internal.dto.ActivityType; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; @@ -16,11 +15,10 @@ public class ActivityTypeDeserializer extends JsonDeserializer { @Override public ActivityType deserialize(JsonParser jsonParser, DeserializationContext context) - throws IOException, JsonProcessingException { + throws IOException { if (jsonParser.getCurrentToken() == JsonToken.VALUE_NUMBER_INT) { return ActivityType.fromId(jsonParser.getNumberValue().intValue()); } throw context.instantiationException(ActivityType.class, "Expected int value to parse an ActivityType"); } - } diff --git a/src/main/java/com/codealike/client/core/internal/serialization/ActivityTypeSerializer.java b/src/main/java/com/codealike/client/core/internal/serialization/ActivityTypeSerializer.java index ec9a91f..dd10902 100644 --- a/src/main/java/com/codealike/client/core/internal/serialization/ActivityTypeSerializer.java +++ b/src/main/java/com/codealike/client/core/internal/serialization/ActivityTypeSerializer.java @@ -5,7 +5,6 @@ import com.codealike.client.core.internal.dto.ActivityType; import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; @@ -14,9 +13,7 @@ public class ActivityTypeSerializer extends JsonSerializer { @Override - public void serialize(ActivityType type, JsonGenerator jgen, SerializerProvider arg2) throws IOException, - JsonProcessingException { + public void serialize(ActivityType type, JsonGenerator jgen, SerializerProvider arg2) throws IOException { jgen.writeNumber(type.getId()); } - } diff --git a/src/main/java/com/codealike/client/core/internal/serialization/DateTimeDeserializer.java b/src/main/java/com/codealike/client/core/internal/serialization/DateTimeDeserializer.java index 0381452..7f68354 100644 --- a/src/main/java/com/codealike/client/core/internal/serialization/DateTimeDeserializer.java +++ b/src/main/java/com/codealike/client/core/internal/serialization/DateTimeDeserializer.java @@ -3,37 +3,36 @@ */ package com.codealike.client.core.internal.serialization; -import com.codealike.client.core.internal.startup.PluginContext; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; -import org.joda.time.DateTime; -import org.joda.time.format.DateTimeFormatter; import java.io.IOException; +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; -public class DateTimeDeserializer extends JsonDeserializer { +public class DateTimeDeserializer extends JsonDeserializer { @Override - public DateTime deserialize(JsonParser jsonParser, DeserializationContext context) - throws IOException, JsonProcessingException { + public OffsetDateTime deserialize(JsonParser jsonParser, DeserializationContext context) + throws IOException { - DateTimeFormatter formatter = PluginContext.getInstance().getDateTimeParser(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS"); if (jsonParser.getCurrentToken() == JsonToken.VALUE_STRING) { String date = jsonParser.getValueAsString(); String[] tokens = date.split("\\."); if (tokens.length > 1 && tokens[1].length() > 3) { + // Handling fractional seconds String fractionalSecsAsString = tokens[1].replace("Z", ""); int fractionalSecs = Integer.parseInt(fractionalSecsAsString) / 10000; - return formatter.parseDateTime(String.format("%s.%03d", tokens[0], fractionalSecs)); + String formattedDate = String.format("%s.%03dZ", tokens[0], fractionalSecs); + return OffsetDateTime.parse(formattedDate, formatter); } - return formatter.parseDateTime(date); + return OffsetDateTime.parse(date, formatter); } - throw context.instantiationException(DateTime.class, "Expected string value to parse a DateTime"); + throw context.instantiationException(OffsetDateTime.class, "Expected string value to parse an OffsetDateTime"); } - } diff --git a/src/main/java/com/codealike/client/core/internal/serialization/DateTimeSerializer.java b/src/main/java/com/codealike/client/core/internal/serialization/DateTimeSerializer.java index 90e480e..854353a 100644 --- a/src/main/java/com/codealike/client/core/internal/serialization/DateTimeSerializer.java +++ b/src/main/java/com/codealike/client/core/internal/serialization/DateTimeSerializer.java @@ -5,22 +5,19 @@ import com.codealike.client.core.internal.startup.PluginContext; import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; -import org.joda.time.DateTime; import java.io.IOException; +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; -public class DateTimeSerializer extends JsonSerializer { - +public class DateTimeSerializer extends JsonSerializer { @Override - public void serialize(DateTime dateTime, JsonGenerator jgen, SerializerProvider provider) throws IOException, - JsonProcessingException { -// jgen.writeString(String.format("/Date(%d)/", dateTime.getMillis())); - String formattedDate = PluginContext.getInstance().getDateTimeFormatter().print(dateTime); + public void serialize(OffsetDateTime dateTime, JsonGenerator jgen, SerializerProvider provider) throws IOException { + DateTimeFormatter formatter = PluginContext.getInstance().getDateTimeFormatter(); + String formattedDate = dateTime.format(formatter); jgen.writeString(formattedDate); } - } diff --git a/src/main/java/com/codealike/client/core/internal/serialization/DurationDeserializer.java b/src/main/java/com/codealike/client/core/internal/serialization/DurationDeserializer.java new file mode 100644 index 0000000..5476d48 --- /dev/null +++ b/src/main/java/com/codealike/client/core/internal/serialization/DurationDeserializer.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023. All rights reserved to Torc LLC. + */ +package com.codealike.client.core.internal.serialization; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; + +import java.io.IOException; +import java.time.Duration; + +public class DurationDeserializer extends JsonDeserializer { + + @Override + public Duration deserialize(JsonParser jsonParser, DeserializationContext context) + throws IOException { + if (jsonParser.getCurrentToken() == JsonToken.VALUE_STRING) { + try { + return Duration.parse(jsonParser.getValueAsString()); + } catch (Exception exception) { + throw context.instantiationException(Duration.class, "Failed to parse Duration: " + exception.getMessage()); + } + } + + throw context.instantiationException(Duration.class, "Expected string to parse a Duration"); + } +} diff --git a/src/main/java/com/codealike/client/core/internal/serialization/DurationSerializer.java b/src/main/java/com/codealike/client/core/internal/serialization/DurationSerializer.java new file mode 100644 index 0000000..ed13fd4 --- /dev/null +++ b/src/main/java/com/codealike/client/core/internal/serialization/DurationSerializer.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2023. All rights reserved to Torc LLC. + */ +package com.codealike.client.core.internal.serialization; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +import java.io.IOException; +import java.time.Duration; +import java.util.concurrent.TimeUnit; + +public class DurationSerializer extends JsonSerializer { + + + @Override + public void serialize(Duration period, JsonGenerator jgen, SerializerProvider provider) throws IOException { + long millis = period.toMillis(); + long hours = TimeUnit.MILLISECONDS.toHours(millis); + long minutes = TimeUnit.MILLISECONDS.toMinutes(millis) % 60; + long seconds = TimeUnit.MILLISECONDS.toSeconds(millis) % 60; + long milliseconds = millis % 1000; + + // Format the duration as "HH:mm:ss.SSS" + String formattedDuration = String.format("%02d:%02d:%02d.%03d", hours, minutes, seconds, milliseconds); + + jgen.writeString(formattedDuration); + } +} diff --git a/src/main/java/com/codealike/client/core/internal/serialization/JavaTimeModule.java b/src/main/java/com/codealike/client/core/internal/serialization/JavaTimeModule.java new file mode 100644 index 0000000..0cc9161 --- /dev/null +++ b/src/main/java/com/codealike/client/core/internal/serialization/JavaTimeModule.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2023. All rights reserved to Torc LLC. + */ +package com.codealike.client.core.internal.serialization; + +import com.fasterxml.jackson.databind.module.SimpleModule; + +import java.io.Serial; +import java.time.Duration; +import java.time.OffsetDateTime; + +public class JavaTimeModule extends SimpleModule { + + @Serial + private static final long serialVersionUID = -8783171321786654936L; + + public JavaTimeModule() { + addSerializer(Duration.class, new DurationSerializer()); + addDeserializer(Duration.class, new DurationDeserializer()); + + addSerializer(OffsetDateTime.class, new DateTimeSerializer()); + addDeserializer(OffsetDateTime.class, new DateTimeDeserializer()); + } +} diff --git a/src/main/java/com/codealike/client/core/internal/serialization/JodaPeriodModule.java b/src/main/java/com/codealike/client/core/internal/serialization/JodaPeriodModule.java deleted file mode 100644 index cf735a6..0000000 --- a/src/main/java/com/codealike/client/core/internal/serialization/JodaPeriodModule.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (c) 2023. All rights reserved to Torc LLC. - */ -package com.codealike.client.core.internal.serialization; - -import com.fasterxml.jackson.databind.module.SimpleModule; -import org.joda.time.DateTime; -import org.joda.time.Period; - -public class JodaPeriodModule extends SimpleModule { - - private static final long serialVersionUID = -8783171321786654936L; - - public JodaPeriodModule() { - addSerializer(Period.class, new PeriodSerializer()); - addDeserializer(Period.class, new PeriodDeserializer()); - - addSerializer(DateTime.class, new DateTimeSerializer()); - addDeserializer(DateTime.class, new DateTimeDeserializer()); - } -} diff --git a/src/main/java/com/codealike/client/core/internal/serialization/PeriodDeserializer.java b/src/main/java/com/codealike/client/core/internal/serialization/PeriodDeserializer.java deleted file mode 100644 index d6ed8e9..0000000 --- a/src/main/java/com/codealike/client/core/internal/serialization/PeriodDeserializer.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2023. All rights reserved to Torc LLC. - */ -package com.codealike.client.core.internal.serialization; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import org.joda.time.Period; - -import java.io.IOException; - -public class PeriodDeserializer extends JsonDeserializer { - - - public PeriodDeserializer() { - } - - @Override - public Period deserialize(JsonParser jsonParser, DeserializationContext context) - throws IOException, JsonProcessingException { - if (jsonParser.getCurrentToken() == JsonToken.VALUE_STRING) { - Period period = Period.parse(jsonParser.getValueAsString(), PeriodSerializer.FORMATER); - if (period != null) { - return period; - } - } - - throw context.instantiationException(Period.class, "Expected string"); - } - -} diff --git a/src/main/java/com/codealike/client/core/internal/serialization/PeriodSerializer.java b/src/main/java/com/codealike/client/core/internal/serialization/PeriodSerializer.java deleted file mode 100644 index b756704..0000000 --- a/src/main/java/com/codealike/client/core/internal/serialization/PeriodSerializer.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2023. All rights reserved to Torc LLC. - */ -package com.codealike.client.core.internal.serialization; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; -import org.joda.time.Period; -import org.joda.time.format.PeriodFormatter; -import org.joda.time.format.PeriodFormatterBuilder; - -import java.io.IOException; - -public class PeriodSerializer extends JsonSerializer { - - public static final PeriodFormatter FORMATER = new PeriodFormatterBuilder().printZeroAlways().minimumPrintedDigits(2).appendHours().appendLiteral(":"). - appendMinutes().appendLiteral(":").appendSeconds().appendLiteral(".").appendMillis().toFormatter(); - - public PeriodSerializer() { - } - - @Override - public void serialize(Period period, JsonGenerator jgen, SerializerProvider provider) throws IOException, - JsonProcessingException { - jgen.writeString(FORMATER.print(period.toPeriod())); - } - - -} diff --git a/src/main/java/com/codealike/client/core/internal/services/IdentityService.java b/src/main/java/com/codealike/client/core/internal/services/IdentityService.java index 6e205c7..4def63d 100644 --- a/src/main/java/com/codealike/client/core/internal/services/IdentityService.java +++ b/src/main/java/com/codealike/client/core/internal/services/IdentityService.java @@ -55,7 +55,7 @@ public boolean isAuthenticated() { } public boolean login(String identity, String token, boolean storeCredentials, boolean rememberMe) { - Notification note = new Notification("CodealikeApplicationComponent.Notifications", + Notification note = new Notification("CodealikeLifecycleListener.Notifications", "Codealike", "Codealike is connecting...", NotificationType.INFORMATION); @@ -190,7 +190,7 @@ public boolean isCredentialsStored() { } public void logOff() { - Notification note = new Notification("CodealikeApplicationComponent.Notifications", + Notification note = new Notification("CodealikeLifecycleListener.Notifications", "Codealike", "Codealike is disconnecting...", NotificationType.INFORMATION); diff --git a/src/main/java/com/codealike/client/core/internal/services/TrackingService.java b/src/main/java/com/codealike/client/core/internal/services/TrackingService.java index 4fe8a55..f2f8e9e 100644 --- a/src/main/java/com/codealike/client/core/internal/services/TrackingService.java +++ b/src/main/java/com/codealike/client/core/internal/services/TrackingService.java @@ -15,8 +15,8 @@ import com.intellij.notification.Notifications; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; -import org.joda.time.DateTime; +import java.time.OffsetDateTime; import java.util.UUID; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -31,9 +31,10 @@ public class TrackingService extends BaseService { private static TrackingService _instance; - private TrackedProjectManager trackedProjectManager; + private final TrackedProjectManager trackedProjectManager; + private final StateTracker tracker; + private ScheduledExecutorService flushExecutor = null; - private StateTracker tracker; private boolean isTracking; private PluginContext context; @@ -97,11 +98,11 @@ public void run() { } private void flushTrackingInformation() { - Boolean verboseMode = Boolean.parseBoolean(context.getProperty("activity-verbose-notifications")); + boolean verboseMode = Boolean.parseBoolean(context.getProperty("activity-verbose-notifications")); Notification resultNote = null; - Notification note = new Notification("CodealikeApplicationComponent.Notifications", + Notification note = new Notification("CodealikeLifecycleListener.Notifications", "Codealike", "Codealike is sending activities...", NotificationType.INFORMATION); @@ -112,7 +113,7 @@ private void flushTrackingInformation() { FlushResult result = tracker.flush(context.getIdentityService().getIdentity(), context.getIdentityService().getToken()); switch (result) { case Succeded: - resultNote = new Notification("CodealikeApplicationComponent.Notifications", + resultNote = new Notification("CodealikeLifecycleListener.Notifications", "Codealike", "Codealike sent activities", NotificationType.INFORMATION); @@ -121,7 +122,7 @@ private void flushTrackingInformation() { break; case Skip: - resultNote = new Notification("CodealikeApplicationComponent.Notifications", + resultNote = new Notification("CodealikeLifecycleListener.Notifications", "Codealike", "No data to be sent", NotificationType.INFORMATION); @@ -132,7 +133,7 @@ private void flushTrackingInformation() { break; case Offline: - resultNote = new Notification("CodealikeApplicationComponent.Notifications", + resultNote = new Notification("CodealikeLifecycleListener.Notifications", "Codealike", "Codealike is working in offline mode", NotificationType.INFORMATION); @@ -141,7 +142,7 @@ private void flushTrackingInformation() { break; case Report: - resultNote = new Notification("CodealikeApplicationComponent.Notifications", + resultNote = new Notification("CodealikeLifecycleListener.Notifications", "Codealike", "Codealike is storing corrupted entries for further inspection", NotificationType.INFORMATION); @@ -171,7 +172,7 @@ public void enableTracking() { if (context.isAuthenticated()) { startTracking(); - Notification note = new Notification("CodealikeApplicationComponent.Notifications", + Notification note = new Notification("CodealikeLifecycleListener.Notifications", "Codealike", "Codealike is connected and tracking your projects.", NotificationType.INFORMATION); @@ -186,7 +187,7 @@ public void disableTracking() { // flush last information before leaving flushTrackingInformation(); - Notification note = new Notification("CodealikeApplicationComponent.Notifications", + Notification note = new Notification("CodealikeLifecycleListener.Notifications", "Codealike", "Codealike is not tracking your projects", NotificationType.INFORMATION); @@ -194,7 +195,7 @@ public void disableTracking() { } } - public synchronized void startTracking(Project project, DateTime workspaceInitDate) { + public synchronized void startTracking(Project project, OffsetDateTime workspaceInitDate) { if (!project.isOpen()) { return; } diff --git a/src/main/java/com/codealike/client/core/internal/startup/PluginContext.java b/src/main/java/com/codealike/client/core/internal/startup/PluginContext.java index 3a9a98c..42f6076 100644 --- a/src/main/java/com/codealike/client/core/internal/startup/PluginContext.java +++ b/src/main/java/com/codealike/client/core/internal/startup/PluginContext.java @@ -9,7 +9,7 @@ import com.codealike.client.core.internal.dto.SolutionContextInfo; import com.codealike.client.core.internal.dto.Version; import com.codealike.client.core.internal.model.ProjectSettings; -import com.codealike.client.core.internal.serialization.JodaPeriodModule; +import com.codealike.client.core.internal.serialization.JavaTimeModule; import com.codealike.client.core.internal.services.IdentityService; import com.codealike.client.core.internal.services.TrackingService; import com.codealike.client.core.internal.tracking.code.ContextCreator; @@ -24,17 +24,16 @@ import com.intellij.openapi.components.impl.stores.IProjectStore; import com.intellij.openapi.project.Project; import com.intellij.project.ProjectKt; -import org.joda.time.DateTime; -import org.joda.time.DateTimeZone; -import org.joda.time.format.DateTimeFormat; -import org.joda.time.format.DateTimeFormatter; -import org.joda.time.format.DateTimeFormatterBuilder; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; import java.nio.file.Path; import java.security.KeyManagementException; +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.temporal.ChronoField; import java.util.Properties; import java.util.Random; import java.util.UUID; @@ -47,41 +46,51 @@ */ @SuppressWarnings("restriction") public class PluginContext { - public static final String VERSION = "1.6.0.0"; public static final UUID UNASSIGNED_PROJECT = UUID.fromString("00000000-0000-0000-0000-0000000001"); - private static final String PLUGIN_PREFERENCES_QUALIFIER = "com.codealike.client.intellij"; + private static final String VERSION = "1.6.0.0"; + private static PluginContext _instance; - private String ideName; - private Version protocolVersion; - private Properties properties; - private ObjectWriter jsonWriter; - private ObjectMapper jsonMapper; - private ContextCreator contextCreator; - private DateTimeFormatter dateTimeFormatter; - private DateTimeFormatter dateTimeParser; - private IdentityService identityService; + + private final String ideName; + private final Version protocolVersion; + private final Properties properties; + private final ObjectWriter jsonWriter; + private final ObjectMapper jsonMapper; + private final ContextCreator contextCreator; + private final DateTimeFormatter dateTimeFormatter; + private final IdentityService identityService; + private final String instanceValue; + private final String machineName; + private final Configuration configuration; + private TrackingService trackingService; - private String instanceValue; - private String machineName; - private Configuration configuration; public PluginContext(Properties properties) { - DateTimeZone.setDefault(DateTimeZone.UTC); ObjectMapper mapper = new ObjectMapper(); - mapper.registerModule(new JodaPeriodModule()); + mapper.registerModule(new JavaTimeModule()); mapper.setSerializationInclusion(Include.NON_NULL); this.jsonWriter = mapper.writer().withDefaultPrettyPrinter(); this.jsonMapper = mapper; this.contextCreator = new ContextCreator(); - this.dateTimeParser = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSS"); - this.dateTimeFormatter = new DateTimeFormatterBuilder().appendYear(4, 4).appendLiteral("-"). - appendMonthOfYear(2).appendLiteral("-").appendDayOfMonth(2). - appendLiteral("T").appendHourOfDay(2).appendLiteral(":"). - appendMinuteOfHour(2).appendLiteral(":").appendSecondOfMinute(2). - appendLiteral(".").appendMillisOfSecond(3).appendLiteral("Z").toFormatter(); + this.dateTimeFormatter = new DateTimeFormatterBuilder() + .appendValue(ChronoField.YEAR, 4) + .appendLiteral("-") + .appendValue(ChronoField.MONTH_OF_YEAR, 2) + .appendLiteral("-") + .appendValue(ChronoField.DAY_OF_MONTH, 2) + .appendLiteral("T") + .appendValue(ChronoField.HOUR_OF_DAY, 2) + .appendLiteral(":") + .appendValue(ChronoField.MINUTE_OF_HOUR, 2) + .appendLiteral(":") + .appendValue(ChronoField.SECOND_OF_MINUTE, 2) + .appendLiteral(".") + .appendValue(ChronoField.MILLI_OF_SECOND, 3) + .appendLiteral("Z") + .toFormatter(); this.identityService = IdentityService.getInstance(); - this.instanceValue = String.valueOf(new Random(DateTime.now().getMillis()).nextInt(Integer.MAX_VALUE) + 1); + this.instanceValue = String.valueOf(new Random(OffsetDateTime.now().toInstant().toEpochMilli()).nextInt(Integer.MAX_VALUE) + 1); this.protocolVersion = new Version(0, 9); this.properties = properties; this.ideName = ApplicationNamesInfo.getInstance().getLowercaseProductName(); @@ -245,8 +254,7 @@ private UUID tryCreateUniqueId() { LogManager.INSTANCE.logInfo("Communication problems running in offline mode."); return solutionId; } - int numberOfRetries = 0; - while (response.conflict() || (response.error() && numberOfRetries < ApiClient.MAX_RETRIES)) { + while (response.conflict() || response.error()) { solutionId = UUID.randomUUID(); response = client.getSolutionContext(solutionId); } @@ -318,7 +326,7 @@ public boolean checkVersion() { private void showIcompatibleVersionDialog() { String title = "This version is not updated"; - String text = "Click below to be on the bleeding edge and enjoy an improved version of CodealikeApplicationComponent."; + String text = "Click below to be on the bleeding edge and enjoy an improved version of Codealike."; /*ErrorDialogView dialog = new ErrorDialogView(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), title, text, "Download the latest.", "images/bigCodealike.jpg", new Runnable() { @@ -354,10 +362,6 @@ public DateTimeFormatter getDateTimeFormatter() { return this.dateTimeFormatter; } - public DateTimeFormatter getDateTimeParser() { - return this.dateTimeParser; - } - public IdentityService getIdentityService() { return identityService; } diff --git a/src/main/java/com/codealike/client/core/internal/tracking/ActivitiesRecorder.java b/src/main/java/com/codealike/client/core/internal/tracking/ActivitiesRecorder.java index 36132c5..83c67e0 100644 --- a/src/main/java/com/codealike/client/core/internal/tracking/ActivitiesRecorder.java +++ b/src/main/java/com/codealike/client/core/internal/tracking/ActivitiesRecorder.java @@ -16,8 +16,6 @@ import com.codealike.client.core.internal.utils.LogManager; import com.codealike.client.core.internal.utils.TrackingConsole; import com.fasterxml.jackson.databind.ObjectWriter; -import org.joda.time.DateTime; -import org.joda.time.Period; import java.io.File; import java.io.FileInputStream; @@ -26,6 +24,8 @@ import java.net.UnknownHostException; import java.nio.charset.Charset; import java.security.KeyManagementException; +import java.time.Duration; +import java.time.OffsetDateTime; import java.util.LinkedList; import java.util.List; @@ -36,19 +36,19 @@ public class ActivitiesRecorder { private ActivityEvent lastEvent; private ActivityState lastState; - private PluginContext context; + private final PluginContext context; - private DateTime currentBatchStart; - private DateTime lastEventTime; + private OffsetDateTime currentBatchStart; + private OffsetDateTime lastEventTime; public ActivitiesRecorder(PluginContext context) { this.states = new LinkedList<>(); this.events = new LinkedList<>(); this.context = context; - this.currentBatchStart = DateTime.now(); + this.currentBatchStart = OffsetDateTime.now(); } - public DateTime getLastEventTime() { + public OffsetDateTime getLastEventTime() { return lastEventTime; } @@ -97,25 +97,25 @@ public void updateEndableEntityAsOfNowIfRequired(IEndable endableEntity) { // get idle max interval in seconds int idleMinIntervalInSeconds = PluginContext.getInstance().getConfiguration().getIdleCheckInterval() / 1000; - DateTime currentTime = DateTime.now(); - DateTime entityBaseEnd = endableEntity.getCreationTime().plus(endableEntity.getDuration()); - int elapsedPeriodBetweenLastEventAndNow = new Period(entityBaseEnd, currentTime).toStandardSeconds().getSeconds(); + OffsetDateTime currentTime = OffsetDateTime.now(); + OffsetDateTime entityBaseEnd = endableEntity.getCreationTime().plus(endableEntity.getDuration()); + long elapsedPeriodBetweenLastEventAndNow = Duration.between(entityBaseEnd, currentTime).getSeconds(); // if time elapsed between last event activity and now is less than // the time it takes to infer user was idle, we track the time as it is // else, something happened and idle check was not doing it work, so - // we consider the duration to be as much as a complete idle period + // we consider the duration to be as much as a complete idle Duration if (elapsedPeriodBetweenLastEventAndNow <= idleMinIntervalInSeconds) { - endableEntity.setDuration(new Period(endableEntity.getCreationTime(), currentTime)); + endableEntity.setDuration(Duration.between(endableEntity.getCreationTime(), currentTime)); } else { // if event/state type is system related we track // whatever it is (no exceptions or checks about duration) if (endableEntity.getType() == ActivityType.System || endableEntity.getType() == ActivityType.OpenSolution) { - endableEntity.setDuration(new Period(endableEntity.getCreationTime(), currentTime)); + endableEntity.setDuration(Duration.between(endableEntity.getCreationTime(), currentTime)); } else { // else, we ensure it does not have inconsistent time - endableEntity.setDuration(new Period(endableEntity.getCreationTime(), entityBaseEnd.plusSeconds(idleMinIntervalInSeconds).toDateTime())); + endableEntity.setDuration(Duration.between(endableEntity.getCreationTime(), entityBaseEnd.plusSeconds(idleMinIntervalInSeconds))); } } @@ -181,7 +181,7 @@ public synchronized ActivityEvent recordEvent(ActivityEvent event) { } // saves time from last event - this.lastEventTime = DateTime.now(); + this.lastEventTime = OffsetDateTime.now(); TrackingConsole.getInstance().trackEvent(this.lastEvent); @@ -200,8 +200,8 @@ private Boolean HasOnlyIdleState() { public FlushResult flush(String username, String token) throws UnknownHostException { List statesToSend = null; List eventsToSend = null; - DateTime batchStart = currentBatchStart; - DateTime batchEnd = DateTime.now(); + OffsetDateTime batchStart = currentBatchStart; + OffsetDateTime batchEnd = OffsetDateTime.now(); // if lastState or lastEvent are null then there is no info to flush // so lets skip this attempt @@ -229,7 +229,7 @@ public FlushResult flush(String username, String token) throws UnknownHostExcept this.events = new LinkedList<>(); this.recordEvent(lastEvent.recreate()); - currentBatchStart = DateTime.now(); + currentBatchStart = OffsetDateTime.now(); } // creates an info procesor diff --git a/src/main/java/com/codealike/client/core/internal/tracking/StateTracker.java b/src/main/java/com/codealike/client/core/internal/tracking/StateTracker.java index 1e4c05a..dc4a8d3 100644 --- a/src/main/java/com/codealike/client/core/internal/tracking/StateTracker.java +++ b/src/main/java/com/codealike/client/core/internal/tracking/StateTracker.java @@ -27,11 +27,16 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.*; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiJavaFile; +import com.intellij.psi.PsiMember; import com.intellij.psi.util.PsiTreeUtil; -import org.joda.time.DateTime; -import org.joda.time.Period; +import java.time.Duration; +import java.time.OffsetDateTime; import java.util.UUID; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -45,11 +50,12 @@ */ public class StateTracker { - private ActivitiesRecorder recorder; + private final ActivitiesRecorder recorder; + private final ContextCreator contextCreator; + private ActivityState lastState; private ActivityEvent lastEvent; - private ContextCreator contextCreator; private ScheduledExecutorService idleDetectionExecutor; private DocumentListener documentListener; @@ -110,7 +116,7 @@ private synchronized StructuralCodeContext gatherEventContextInformation(UUID pr VirtualFile file = FileDocumentManager.getInstance().getFile(editor.getDocument()); // if no file was obtained or file is special ide file 'fragment.java' skip process - if (file == null || file.getName() == "fragment.java") + if (file == null || file.getName().equals("fragment.java")) return null; // create code context and populate with event information @@ -192,7 +198,7 @@ public synchronized void trackCodingEvent(Editor editor, int offset, int line) { } } - public void startTrackingProject(Project project, UUID projectId, DateTime startWorkspaceDate) { + public void startTrackingProject(Project project, UUID projectId, OffsetDateTime startWorkspaceDate) { ActivityEvent openSolutionEvent = new ActivityEvent(projectId, ActivityType.OpenSolution, contextCreator.createCodeContext(project)); openSolutionEvent.setCreationTime(startWorkspaceDate); @@ -294,9 +300,9 @@ private void checkIdleStatus() { if (recorder.getLastState().getType() == ActivityType.Idle) { recorder.updateLastState(); } else { - DateTime currentTime = DateTime.now(); + OffsetDateTime currentTime = OffsetDateTime.now(); long idleMaxPeriodInSeconds = PluginContext.getInstance().getConfiguration().getIdleMinInterval() / 1000; - long elapsedFromLastEventInSeconds = new Period(recorder.getLastEventTime(), currentTime).toStandardSeconds().getSeconds(); + long elapsedFromLastEventInSeconds = Duration.between(recorder.getLastEventTime(), currentTime).getSeconds(); if (elapsedFromLastEventInSeconds >= idleMaxPeriodInSeconds) { // not needed because idea cannot track another type than coding // save last state type before going iddle diff --git a/src/main/java/com/codealike/client/core/internal/utils/LogManager.java b/src/main/java/com/codealike/client/core/internal/utils/LogManager.java index 7ad6ef3..fdcf534 100644 --- a/src/main/java/com/codealike/client/core/internal/utils/LogManager.java +++ b/src/main/java/com/codealike/client/core/internal/utils/LogManager.java @@ -20,22 +20,22 @@ public LogManager() { } public void logError(String msg) { - logger.error("CodealikeApplicationComponent: " + msg); + logger.error("CodealikeLifecycleListener: " + msg); } public void logError(Throwable t, String msg) { - logger.error("CodealikeApplicationComponent: " + msg, t); + logger.error("CodealikeLifecycleListener: " + msg, t); } public void logWarn(String msg) { - logger.warn("CodealikeApplicationComponent: " + msg); + logger.warn("CodealikeLifecycleListener: " + msg); } public void logWarn(Throwable t, String msg) { - logger.warn("CodealikeApplicationComponent: " + msg, t); + logger.warn("CodealikeLifecycleListener: " + msg, t); } public void logInfo(String msg) { - logger.info("CodealikeApplicationComponent: " + msg); + logger.info("CodealikeLifecycleListener: " + msg); } } diff --git a/src/main/java/com/codealike/client/core/internal/utils/TrackingConsole.java b/src/main/java/com/codealike/client/core/internal/utils/TrackingConsole.java index 1e40730..2659ec0 100644 --- a/src/main/java/com/codealike/client/core/internal/utils/TrackingConsole.java +++ b/src/main/java/com/codealike/client/core/internal/utils/TrackingConsole.java @@ -3,15 +3,14 @@ */ package com.codealike.client.core.internal.utils; -import java.util.UUID; - -import org.joda.time.format.PeriodFormatter; - import com.codealike.client.core.internal.model.ActivityEvent; import com.codealike.client.core.internal.model.ActivityState; -import com.codealike.client.core.internal.serialization.PeriodSerializer; import com.codealike.client.core.internal.startup.PluginContext; +import java.time.Duration; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + /** * Tracking console class. Used to print track events messages to console. * @@ -22,9 +21,9 @@ public class TrackingConsole { // Singleton instance private static TrackingConsole _instance; // The plugin context instance - private PluginContext context; + private final PluginContext context; // Flag to enable messages to the console - private boolean enabled; + private final boolean enabled; // private constructor private TrackingConsole(PluginContext context) { @@ -66,8 +65,8 @@ public void trackMessage(String message) { public void trackEvent(ActivityEvent event) { if (enabled) { System.out.println("---------------------------------------------------------------------"); - String formattedDate = context.getDateTimeFormatter().print(event.getCreationTime()); - System.out.println(String.format("Event: type:%s, time:%s", event.getType().toString(), formattedDate)); + String formattedDate = context.getDateTimeFormatter().format(event.getCreationTime()); + System.out.printf("Event: type:%s, time:%s%n", event.getType().toString(), formattedDate); System.out.println(event.getContext().toString()); System.out.println("---------------------------------------------------------------------"); } @@ -80,8 +79,22 @@ public void trackEvent(ActivityEvent event) { */ public void trackState(ActivityState state) { if (enabled) { - PeriodFormatter formatter = PeriodSerializer.FORMATER; - System.out.println(String.format("Last recorded state: type:%s, duration:%s\n", state.getType().toString(), state.getDuration().toString(formatter))); + Duration duration = state.getDuration(); + + long millis = duration.toMillis(); + long absMillis = Math.abs(millis); + + long hours = TimeUnit.MILLISECONDS.toHours(absMillis); + long minutes = TimeUnit.MILLISECONDS.toMinutes(absMillis) % 60; + long seconds = TimeUnit.MILLISECONDS.toSeconds(absMillis) % 60; + long milliseconds = absMillis % 1000; + + String formattedDuration = String.format("%s%02d:%02d:%02d.%03d", + (millis < 0 ? "-" : ""), hours, minutes, seconds, milliseconds); + + System.out.printf("Last recorded state: type:%s, duration:%s\n%n", + state.getType().toString(), + formattedDuration); } } @@ -93,7 +106,7 @@ public void trackState(ActivityState state) { */ public void trackProjectEnd(String name, UUID id) { if (enabled) { - System.out.println(String.format("Stopped tracking project \"%s\" with id %s", name, id)); + System.out.printf("Stopped tracking project \"%s\" with id %s%n", name, id); } } @@ -105,7 +118,7 @@ public void trackProjectEnd(String name, UUID id) { */ public void trackProjectStart(String name, UUID id) { if (enabled) { - System.out.println(String.format("Started tracking project \"%s\" with id %s", name, id)); + System.out.printf("Started tracking project \"%s\" with id %s%n", name, id); } } diff --git a/src/main/java/com/codealike/client/intellij/CodealikeApplicationComponent.java b/src/main/java/com/codealike/client/intellij/CodealikeApplicationComponent.java deleted file mode 100644 index 8a9fd51..0000000 --- a/src/main/java/com/codealike/client/intellij/CodealikeApplicationComponent.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (c) 2022-2023. All rights reserved to Torc LLC. - */ -package com.codealike.client.intellij; - -import com.codealike.client.core.api.ApiClient; -import com.codealike.client.core.internal.dto.HealthInfo; -import com.codealike.client.core.internal.services.ServiceListener; -import com.codealike.client.core.internal.startup.PluginContext; -import com.codealike.client.core.internal.utils.LogManager; -import com.codealike.client.intellij.ui.AuthenticationDialog; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.components.ApplicationComponent; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectManager; -import org.jetbrains.annotations.NotNull; - -import java.io.IOException; -import java.io.InputStream; -import java.security.KeyManagementException; -import java.util.Properties; - -/** - * Plugin application component class. - * - * @author Daniel, pvmagacho - * @version 1.6.0.0 - */ -public class CodealikeApplicationComponent implements ApplicationComponent { - private static final String CODEALIKE_PROPERTIES_FILE = "/codealike.properties"; - ServiceListener loginObserver = () -> reloadOpenedProjects(); - private PluginContext pluginContext; - - public CodealikeApplicationComponent() { - } - - @Override - public void initComponent() { - // TODO: insert component initialization logic here - LogManager.INSTANCE.logInfo("CodealikeApplicationComponent plugin initialized."); - - start(); - } - - @Override - public void disposeComponent() { - // TODO: insert component disposal logic here - } - - @Override - @NotNull - public String getComponentName() { - return "CodealikeApplicationComponent"; - } - - protected void start() { - - // load plugin properties - Properties properties = new Properties(); - try { - properties = loadPluginProperties(); - } catch (IOException e) { - e.printStackTrace(); - } - - // initialize plugin context with properties - this.pluginContext = PluginContext.getInstance(properties); - - try { - pluginContext.initializeContext(); - - if (!pluginContext.checkVersion()) { - throw new Exception(); - } - - pluginContext.getIdentityService().addListener(loginObserver); - if (!pluginContext.getIdentityService().tryLoginWithStoredCredentials()) { - authenticate(); - } - } catch (Exception e) { - try { - ApiClient client = ApiClient.tryCreateNew(); - client.logHealth(new HealthInfo(e, "Plugin could not start.", "intellij", HealthInfo.HealthInfoType.Error, pluginContext.getIdentityService().getIdentity())); - } catch (KeyManagementException e1) { - e1.printStackTrace(); - LogManager.INSTANCE.logError(e, "Couldn't send HealtInfo."); - } - LogManager.INSTANCE.logError(e, "Couldn't start plugin."); - } - } - - protected Properties loadPluginProperties() throws IOException { - Properties properties = new Properties(); - InputStream in = CodealikeApplicationComponent.class.getResourceAsStream(CODEALIKE_PROPERTIES_FILE); - properties.load(in); - in.close(); - - return properties; - } - - protected void authenticate() { - ApplicationManager.getApplication().invokeLater(() -> { - // prompt for apiKey if it does not already exist - Project project = null; - try { - project = ProjectManager.getInstance().getDefaultProject(); - } catch (NullPointerException e) { - } - - // lets ask for a api key - AuthenticationDialog dialog = new AuthenticationDialog(project); - dialog.show(); - }); - } - - private void reloadOpenedProjects() { - Project[] openProjects = ProjectManager.getInstance().getOpenProjects(); - if (openProjects.length > 0) { - for (Project p : openProjects) { - ProjectManager.getInstance().reloadProject(p); - } - } - } -} diff --git a/src/main/java/com/codealike/client/intellij/CodealikeLifecycleListener.java b/src/main/java/com/codealike/client/intellij/CodealikeLifecycleListener.java new file mode 100644 index 0000000..54cafed --- /dev/null +++ b/src/main/java/com/codealike/client/intellij/CodealikeLifecycleListener.java @@ -0,0 +1,100 @@ +package com.codealike.client.intellij; + +import com.codealike.client.core.api.ApiClient; +import com.codealike.client.core.internal.dto.HealthInfo; +import com.codealike.client.core.internal.model.exception.IncompatibleVersionException; +import com.codealike.client.core.internal.startup.PluginContext; +import com.codealike.client.core.internal.utils.LogManager; +import com.codealike.client.intellij.ui.AuthenticationDialog; +import com.intellij.ide.AppLifecycleListener; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectManager; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.io.InputStream; +import java.security.KeyManagementException; +import java.util.List; +import java.util.Properties; + +/** + * Plugin initialization on startup + * + * @author afomkina + */ +public class CodealikeLifecycleListener implements AppLifecycleListener { + private static final LogManager LOG = LogManager.INSTANCE; + private static final String PROPERTIES_PATH = "/codealike.properties"; + + @Override + public void appFrameCreated(@NotNull List commandLineArgs) { + start(); + LOG.logInfo("Codealike plugin is initialized."); + } + + private void start() { + Properties properties = loadPluginProperties(); + PluginContext pluginContext = PluginContext.getInstance(properties); + + try { + pluginContext.initializeContext(); + if (!pluginContext.checkVersion()) { + throw new IncompatibleVersionException("Incompatible version detected. The application failed to start."); + } + + pluginContext.getIdentityService().addListener(this::reloadOpenedProjects); + if (!pluginContext.getIdentityService().tryLoginWithStoredCredentials()) { + authenticate(); + } + } catch (Exception exception) { + try { + ApiClient client = ApiClient.tryCreateNew(); + client.logHealth( + new HealthInfo( + exception, + "Plugin could not start.", + "intellij", + HealthInfo.HealthInfoType.Error, + pluginContext.getIdentityService().getIdentity() + ) + ); + } catch (KeyManagementException keyManagementException) { + LOG.logError(exception, "Couldn't send HealthInfo."); + } + LOG.logError(exception, "Couldn't start plugin."); + } + } + + private Properties loadPluginProperties() { + Properties properties = new Properties(); + InputStream stream = CodealikeLifecycleListener.class.getResourceAsStream(PROPERTIES_PATH); + try { + properties.load(stream); + if (stream != null) { + stream.close(); + } else { + LOG.logWarn("Properties is null"); + } + } catch (IOException exception) { + LOG.logError("Couldn't get properties: " + exception.getMessage()); + } + + return properties; + } + + private void authenticate() { + ApplicationManager.getApplication().invokeLater(() -> { + Project project = ProjectManager.getInstance().getDefaultProject(); + AuthenticationDialog dialog = new AuthenticationDialog(project); + dialog.show(); + }); + } + + private void reloadOpenedProjects() { + Project[] openProjects = ProjectManager.getInstance().getOpenProjects(); + for (Project project : openProjects) { + ProjectManager.getInstance().reloadProject(project); + } + } +} diff --git a/src/main/java/com/codealike/client/intellij/CodealikeProjectService.java b/src/main/java/com/codealike/client/intellij/CodealikeProjectService.java index 998fcfe..f748911 100644 --- a/src/main/java/com/codealike/client/intellij/CodealikeProjectService.java +++ b/src/main/java/com/codealike/client/intellij/CodealikeProjectService.java @@ -13,7 +13,7 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.startup.StartupActivity; import org.jetbrains.annotations.NotNull; -import org.joda.time.DateTime; +import java.time.OffsetDateTime; /** * Plugin project service. @@ -46,12 +46,12 @@ void onProjectOpened() { switch (identityService.getTrackActivity()) { case Always: { trackingService.enableTracking(); - trackingService.startTracking(project, DateTime.now()); + trackingService.startTracking(project, OffsetDateTime.now()); break; } case AskEveryTime: case Never: - Notification note = new Notification("CodealikeApplicationComponent.Notifications", + Notification note = new Notification("CodealikeLifecycleListener.Notifications", "Codealike", "Codealike is not tracking your projects", NotificationType.INFORMATION); diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index c8cf7a7..f81b202 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -35,16 +35,14 @@ - - + + - - - com.codealike.client.intellij.CodealikeApplicationComponent - - + + + \ No newline at end of file