From 53b7022d448bd8ff5b9f923329fc5ada95bb0604 Mon Sep 17 00:00:00 2001 From: Hiroki Terashima Date: Tue, 15 Sep 2026 11:14:55 -0700 Subject: [PATCH 1/4] refactor: migrate Apache HttpClient to Spring RestClient --- pom.xml | 5 -- .../portal/domain/admin/DailyAdminJob.java | 44 +++++++------ .../contact/ContactAPIController.java | 63 ++++++++----------- .../webservice/crater/CRaterService.java | 43 +++++-------- 4 files changed, 62 insertions(+), 93 deletions(-) diff --git a/pom.xml b/pom.xml index 51ed1891e..3caacb9c6 100644 --- a/pom.xml +++ b/pom.xml @@ -452,11 +452,6 @@ HikariCP 7.1.0 - - org.apache.httpcomponents - httpclient - 4.5.14 - org.apache.commons commons-lang3 diff --git a/src/main/java/org/wise/portal/domain/admin/DailyAdminJob.java b/src/main/java/org/wise/portal/domain/admin/DailyAdminJob.java index ee1a73886..baa22e8aa 100644 --- a/src/main/java/org/wise/portal/domain/admin/DailyAdminJob.java +++ b/src/main/java/org/wise/portal/domain/admin/DailyAdminJob.java @@ -23,7 +23,7 @@ */ package org.wise.portal.domain.admin; -import java.io.IOException; + import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; @@ -35,14 +35,12 @@ import jakarta.mail.MessagingException; -import org.apache.http.HttpResponse; -import org.apache.http.HttpStatus; -import org.apache.http.NameValuePair; -import org.apache.http.client.HttpClient; -import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.message.BasicNameValuePair; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.MediaType; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestClient; +import org.springframework.web.client.RestClientException; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -526,24 +524,24 @@ public void sendEmail(String message) { public void postStatistics(String wiseStatisticsString) { if (WISE_HUB_URL != null) { - HttpClient client = HttpClientBuilder.create().build(); - HttpPost post = new HttpPost(WISE_HUB_URL); - List urlParameters = new ArrayList(); - urlParameters.add(new BasicNameValuePair("name", appProperties.getProperty("wise.name"))); - urlParameters.add(new BasicNameValuePair("stats", wiseStatisticsString)); + MultiValueMap formData = new LinkedMultiValueMap<>(); + formData.add("name", appProperties.getProperty("wise.name")); + formData.add("stats", wiseStatisticsString); try { - post.setEntity(new UrlEncodedFormEntity(urlParameters)); - HttpResponse response = client.execute(post); - if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { - System.err.println("Method failed: " + response.getStatusLine()); - } - // Use caution: ensure correct character encoding and is not binary data - } catch (IOException e) { + RestClient restClient = RestClient.create(); + restClient.post() + .uri(WISE_HUB_URL) + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + .body(formData) + .retrieve() + .onStatus(HttpStatusCode::isError, (request, response) -> { + System.err.println("Method failed: " + response.getStatusCode()); + }) + .toBodilessEntity(); + } catch (RestClientException e) { System.err.println("Fatal transport error: " + e.getMessage()); e.printStackTrace(); - } finally { - post.releaseConnection(); } } } diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/contact/ContactAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/contact/ContactAPIController.java index 01458cc93..64706094c 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/contact/ContactAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/contact/ContactAPIController.java @@ -1,12 +1,10 @@ package org.wise.portal.presentation.web.controllers.contact; -import org.apache.http.HttpResponse; -import org.apache.http.NameValuePair; -import org.apache.http.client.HttpClient; -import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.message.BasicNameValuePair; +import org.springframework.http.MediaType; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestClient; +import org.springframework.web.client.RestClientException; import org.json.JSONException; import org.json.JSONObject; import org.springframework.beans.factory.annotation.Autowired; @@ -28,13 +26,8 @@ import org.wise.portal.service.user.UserService; import jakarta.mail.MessagingException; -import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStreamReader; -import java.io.UnsupportedEncodingException; import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.List; import java.util.Properties; @RestController @@ -297,8 +290,7 @@ private boolean appPropertiesHasUserAgentParseKey() { } private JSONObject getUserAgentParseResult(String userAgent) throws IOException, JSONException { - HttpPost post = prepareUserAgentParseRequest(userAgent); - JSONObject userAgentResponse = makeUserAgentParseRequest(post); + JSONObject userAgentResponse = makeUserAgentParseRequest(userAgent); if (isUserAgentResponseSuccess(userAgentResponse)) { JSONObject parse = userAgentResponse.getJSONObject("parse"); return parse; @@ -306,33 +298,28 @@ private JSONObject getUserAgentParseResult(String userAgent) throws IOException, return null; } - private HttpPost prepareUserAgentParseRequest(String userAgent) { + private JSONObject makeUserAgentParseRequest(String userAgent) throws IOException, JSONException { String userKey = appProperties.getProperty("userAgentParseKey"); - HttpPost post = new HttpPost(userAgentParseURL); - List urlParameters = new ArrayList(); - urlParameters.add(new BasicNameValuePair("user_key", userKey)); - urlParameters.add(new BasicNameValuePair("user_agent", userAgent)); - try { - post.setEntity(new UrlEncodedFormEntity(urlParameters)); - } catch (UnsupportedEncodingException e) { - - } - return post; - } + MultiValueMap formData = new LinkedMultiValueMap<>(); + formData.add("user_key", userKey); + formData.add("user_agent", userAgent); - private JSONObject makeUserAgentParseRequest(HttpPost post) throws IOException, JSONException { - HttpClient client = HttpClientBuilder.create().build(); - HttpResponse response = client.execute(post); - BufferedReader rd = new BufferedReader( - new InputStreamReader(response.getEntity().getContent())); - StringBuffer userAgentParseResult = new StringBuffer(); - String line = ""; - while ((line = rd.readLine()) != null) { - userAgentParseResult.append(line); + RestClient restClient = RestClient.create(); + try { + String responseBody = restClient.post() + .uri(userAgentParseURL) + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + .body(formData) + .retrieve() + .body(String.class); + + if (responseBody != null) { + return new JSONObject(responseBody); + } + } catch (RestClientException e) { + throw new IOException("Failed to parse user agent", e); } - String parseResultString = userAgentParseResult.toString(); - JSONObject parseResultJSONObject = new JSONObject(parseResultString); - return parseResultJSONObject; + return null; } private boolean isUserAgentResponseSuccess(JSONObject userAgentResponse) { diff --git a/src/main/java/org/wise/vle/domain/webservice/crater/CRaterService.java b/src/main/java/org/wise/vle/domain/webservice/crater/CRaterService.java index 4a94c1712..02784cd48 100644 --- a/src/main/java/org/wise/vle/domain/webservice/crater/CRaterService.java +++ b/src/main/java/org/wise/vle/domain/webservice/crater/CRaterService.java @@ -23,18 +23,10 @@ */ package org.wise.vle.domain.webservice.crater; -import java.io.IOException; -import java.util.Base64; - -import org.apache.commons.io.IOUtils; -import org.apache.http.HttpHeaders; -import org.apache.http.HttpResponse; -import org.apache.http.HttpStatus; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.entity.ContentType; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.client.HttpClientBuilder; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.MediaType; +import org.springframework.web.client.RestClient; +import org.springframework.web.client.RestClientException; import org.json.JSONException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; @@ -78,26 +70,23 @@ public String getCRaterResponse(CRaterRequest request) throws JSONException { * @return the response string from the CRater server */ private String post(CRaterRequest request) throws JSONException { - HttpClient client = HttpClientBuilder.create().build(); - HttpPost post = new HttpPost(request.getCRaterUrl()); try { String password = appProperties.getProperty( request.forBerkeleyEndpoint() ? "berkeley_cRater_password" : "cRater_password"); - String authHeader = "Basic " - + Base64.getEncoder().encodeToString(("extsyscrtr02dev:" + password).getBytes()); - post.setHeader(HttpHeaders.AUTHORIZATION, authHeader); - post.setHeader(HttpHeaders.CONTENT_TYPE, "application/json;charset=utf-8"); - post.setEntity(new StringEntity(request.generateBodyData(), ContentType.APPLICATION_JSON)); - HttpResponse response = client.execute(post); - if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { - System.err.println("Method failed: " + response.getStatusLine()); - } - return IOUtils.toString(response.getEntity().getContent(), "UTF-8"); - } catch (IOException e) { + RestClient restClient = RestClient.create(); + return restClient.post() + .uri(request.getCRaterUrl()) + .headers(headers -> headers.setBasicAuth("extsyscrtr02dev", password)) + .contentType(MediaType.APPLICATION_JSON) + .body(request.generateBodyData()) + .retrieve() + .onStatus(HttpStatusCode::isError, (req, resp) -> { + System.err.println("Method failed: " + resp.getStatusCode()); + }) + .body(String.class); + } catch (RestClientException e) { System.err.println("Fatal transport error: " + e.getMessage()); e.printStackTrace(); - } finally { - post.releaseConnection(); } return null; } From 8230849b0ce7babb3e03cdceaa4e2f99fd545bd3 Mon Sep 17 00:00:00 2001 From: Hiroki Terashima Date: Tue, 15 Sep 2026 11:29:24 -0700 Subject: [PATCH 2/4] refactor: inject RestClient via constructor in CRaterService and update tests to use MockRestServiceServer --- .../webservice/crater/CRaterService.java | 9 ++++-- .../webservice/crater/CRaterServiceTest.java | 31 +++++++++++++++++-- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/wise/vle/domain/webservice/crater/CRaterService.java b/src/main/java/org/wise/vle/domain/webservice/crater/CRaterService.java index 02784cd48..ddd81c5d2 100644 --- a/src/main/java/org/wise/vle/domain/webservice/crater/CRaterService.java +++ b/src/main/java/org/wise/vle/domain/webservice/crater/CRaterService.java @@ -41,8 +41,14 @@ @Service public class CRaterService { + private final Environment appProperties; + private final RestClient restClient; + @Autowired - private Environment appProperties; + public CRaterService(Environment appProperties, RestClient.Builder restClientBuilder) { + this.appProperties = appProperties; + this.restClient = restClientBuilder.build(); + } /** * Sends either student work (scoring request) or an item id (verification request) to @@ -73,7 +79,6 @@ private String post(CRaterRequest request) throws JSONException { try { String password = appProperties.getProperty( request.forBerkeleyEndpoint() ? "berkeley_cRater_password" : "cRater_password"); - RestClient restClient = RestClient.create(); return restClient.post() .uri(request.getCRaterUrl()) .headers(headers -> headers.setBasicAuth("extsyscrtr02dev", password)) diff --git a/src/test/java/org/wise/vle/domain/webservice/crater/CRaterServiceTest.java b/src/test/java/org/wise/vle/domain/webservice/crater/CRaterServiceTest.java index 67abab653..ec7bdcf81 100644 --- a/src/test/java/org/wise/vle/domain/webservice/crater/CRaterServiceTest.java +++ b/src/test/java/org/wise/vle/domain/webservice/crater/CRaterServiceTest.java @@ -1,24 +1,32 @@ package org.wise.vle.domain.webservice.crater; import static org.easymock.EasyMock.*; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.*; +import static org.springframework.test.web.client.response.MockRestResponseCreators.*; import org.easymock.EasyMockExtension; import org.easymock.Mock; import org.easymock.TestSubject; import org.json.JSONException; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.core.env.Environment; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.web.client.RestClient; @ExtendWith(EasyMockExtension.class) public class CRaterServiceTest { - @TestSubject - private CRaterService cRaterService = new CRaterService(); + private CRaterService cRaterService; @Mock private Environment appProperties; + private MockRestServiceServer mockServer; + private String clientId = "wise-test"; private String itemId = "test-item-id"; private String password = "abc123"; @@ -27,6 +35,13 @@ public class CRaterServiceTest { private String berkeleyScoringUrl = "https://test.org/score/berkeley"; private String berkeleyVerifyUrl = "https://test.org/verify/berkeley"; + @BeforeEach + public void setUp() { + RestClient.Builder builder = RestClient.builder(); + mockServer = MockRestServiceServer.bindTo(builder).build(); + cRaterService = new CRaterService(appProperties, builder); + } + public void beforeETS() { expect(appProperties.getProperty("cRater_client_id")).andReturn(clientId); expect(appProperties.getProperty("cRater_password")).andReturn(password); @@ -46,8 +61,11 @@ public void getScoringResponse_ShouldGetCRaterProperties() throws JSONException request.setResponseText("hello"); expect(appProperties.getProperty("cRater_scoring_url")).andReturn(scoringUrl); replay(appProperties); + mockServer.expect(requestTo(scoringUrl)).andExpect(method(HttpMethod.POST)) + .andRespond(withSuccess("{}", MediaType.APPLICATION_JSON)); cRaterService.getCRaterResponse(request); verify(appProperties); + mockServer.verify(); } @Test @@ -57,8 +75,11 @@ public void getVerificationResponse_ShouldGetCRaterProperties() throws JSONExcep request.setItemId(itemId); expect(appProperties.getProperty("cRater_verification_url")).andReturn(verifyUrl); replay(appProperties); + mockServer.expect(requestTo(verifyUrl)).andExpect(method(HttpMethod.POST)) + .andRespond(withSuccess("{}", MediaType.APPLICATION_JSON)); cRaterService.getCRaterResponse(request); verify(appProperties); + mockServer.verify(); } @Test @@ -70,8 +91,11 @@ public void getBerkeleyScoringResponse_ShouldGetCRaterProperties() throws JSONEx request.setResponseText("hello"); expect(appProperties.getProperty("berkeley_cRater_scoring_url")).andReturn(berkeleyScoringUrl); replay(appProperties); + mockServer.expect(requestTo(berkeleyScoringUrl)).andExpect(method(HttpMethod.POST)) + .andRespond(withSuccess("{}", MediaType.APPLICATION_JSON)); cRaterService.getCRaterResponse(request); verify(appProperties); + mockServer.verify(); } @Test @@ -82,7 +106,10 @@ public void getBerkeleyVerificationResponse_ShouldGetCRaterProperties() throws J expect(appProperties.getProperty("berkeley_cRater_verification_url")) .andReturn(berkeleyVerifyUrl); replay(appProperties); + mockServer.expect(requestTo(berkeleyVerifyUrl)).andExpect(method(HttpMethod.POST)) + .andRespond(withSuccess("{}", MediaType.APPLICATION_JSON)); cRaterService.getCRaterResponse(request); verify(appProperties); + mockServer.verify(); } } From 94d9724ff3c2c90f4779d221fc71f54bdeef50fb Mon Sep 17 00:00:00 2001 From: Hiroki Terashima Date: Tue, 15 Sep 2026 14:43:38 -0700 Subject: [PATCH 3/4] fix: update content type to APPLICATION_JSON_UTF8 in CRaterService --- .../vle/domain/webservice/crater/CRaterService.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/wise/vle/domain/webservice/crater/CRaterService.java b/src/main/java/org/wise/vle/domain/webservice/crater/CRaterService.java index ddd81c5d2..cbb60f69e 100644 --- a/src/main/java/org/wise/vle/domain/webservice/crater/CRaterService.java +++ b/src/main/java/org/wise/vle/domain/webservice/crater/CRaterService.java @@ -79,16 +79,12 @@ private String post(CRaterRequest request) throws JSONException { try { String password = appProperties.getProperty( request.forBerkeleyEndpoint() ? "berkeley_cRater_password" : "cRater_password"); - return restClient.post() - .uri(request.getCRaterUrl()) + return restClient.post().uri(request.getCRaterUrl()) .headers(headers -> headers.setBasicAuth("extsyscrtr02dev", password)) - .contentType(MediaType.APPLICATION_JSON) - .body(request.generateBodyData()) - .retrieve() + .contentType(MediaType.APPLICATION_JSON_UTF8).body(request.generateBodyData()).retrieve() .onStatus(HttpStatusCode::isError, (req, resp) -> { System.err.println("Method failed: " + resp.getStatusCode()); - }) - .body(String.class); + }).body(String.class); } catch (RestClientException e) { System.err.println("Fatal transport error: " + e.getMessage()); e.printStackTrace(); From 4e39fc205a458f386b52e85babc1ad57e6abeb16 Mon Sep 17 00:00:00 2001 From: Hiroki Terashima Date: Tue, 15 Sep 2026 14:47:34 -0700 Subject: [PATCH 4/4] refactor: remove unused imports and format RestClient calls in DailyAdminJob --- .../wise/portal/domain/admin/DailyAdminJob.java | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/wise/portal/domain/admin/DailyAdminJob.java b/src/main/java/org/wise/portal/domain/admin/DailyAdminJob.java index baa22e8aa..cb8645e75 100644 --- a/src/main/java/org/wise/portal/domain/admin/DailyAdminJob.java +++ b/src/main/java/org/wise/portal/domain/admin/DailyAdminJob.java @@ -23,7 +23,6 @@ */ package org.wise.portal.domain.admin; - import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; @@ -50,7 +49,6 @@ import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; -import org.wise.portal.dao.ObjectNotFoundException; import org.wise.portal.dao.run.RunDao; import org.wise.portal.dao.portal.PortalStatisticsDao; import org.wise.portal.dao.project.ProjectDao; @@ -58,7 +56,6 @@ import org.wise.portal.domain.authentication.MutableUserDetails; import org.wise.portal.domain.authentication.impl.StudentUserDetails; import org.wise.portal.domain.authentication.impl.TeacherUserDetails; -import org.wise.portal.domain.portal.Portal; import org.wise.portal.domain.portal.PortalStatistics; import org.wise.portal.domain.portal.impl.PortalStatisticsImpl; import org.wise.portal.domain.project.Project; @@ -530,15 +527,10 @@ public void postStatistics(String wiseStatisticsString) { try { RestClient restClient = RestClient.create(); - restClient.post() - .uri(WISE_HUB_URL) - .contentType(MediaType.APPLICATION_FORM_URLENCODED) - .body(formData) - .retrieve() - .onStatus(HttpStatusCode::isError, (request, response) -> { + restClient.post().uri(WISE_HUB_URL).contentType(MediaType.APPLICATION_FORM_URLENCODED) + .body(formData).retrieve().onStatus(HttpStatusCode::isError, (request, response) -> { System.err.println("Method failed: " + response.getStatusCode()); - }) - .toBodilessEntity(); + }).toBodilessEntity(); } catch (RestClientException e) { System.err.println("Fatal transport error: " + e.getMessage()); e.printStackTrace();