Skip to content

Commit

Permalink
Merge pull request #119 from bcgov/develop/alex-GRAD2-2410
Browse files Browse the repository at this point in the history
Develop/alex grad2 2410
  • Loading branch information
kamal-mohammed authored Nov 23, 2023
2 parents 6cbae9a + 811945e commit 0d3a98d
Show file tree
Hide file tree
Showing 12 changed files with 313 additions and 70 deletions.
7 changes: 6 additions & 1 deletion api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
</parent>
<groupId>ca.bc.gov</groupId>
<artifactId>educ-grad-business-api</artifactId>
<version>1.8.25</version>
<version>1.8.26</version>
<name>educ-grad-business-api</name>
<description>GRAD Business API for external clients</description>

Expand Down Expand Up @@ -170,6 +170,11 @@
<artifactId>pdfbox</artifactId>
<version>2.0.26</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20220320</version>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public WebClient webClient() {
return WebClient.builder().exchangeStrategies(ExchangeStrategies.builder()
.codecs(configurer -> configurer
.defaultCodecs()
.maxInMemorySize(20 * 1024 * 1024))
.maxInMemorySize(100 * 1024 * 1024))
.build()).build();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package ca.bc.gov.educ.api.gradbusiness.config;

import ca.bc.gov.educ.api.gradbusiness.model.dto.ResponseObjCache;
import ca.bc.gov.educ.api.gradbusiness.util.EducGraduationApiConstants;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class TokenUtilsConfig {

EducGraduationApiConstants constants;

@Autowired
public TokenUtilsConfig(EducGraduationApiConstants constants) {
this.constants = constants;
}

@Bean
public ResponseObjCache createResponseObjCache() {
return new ResponseObjCache(constants.getTokenExpiryOffset());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package ca.bc.gov.educ.api.gradbusiness.model.dto;

import lombok.Data;

@Data
public class ResponseObj {

private String access_token;
private String refresh_token;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package ca.bc.gov.educ.api.gradbusiness.model.dto;

import org.json.JSONObject;

import java.util.Base64;

public class ResponseObjCache {

private long tokenExpiry = 0;
private ResponseObj responseObj;

// tokenExpiry-[seconds] provides a slight offset, if token WILL expire in
// [seconds], obtain a new one
private int offset;

public ResponseObjCache(int offset) {
this.offset = offset;
}

public ResponseObj getResponseObj() {
return responseObj;
}

public void setResponseObj(ResponseObj responseObj) {
this.setTokenExpiry(responseObj);
this.responseObj = responseObj;
}

public boolean isExpired(){
// tokenExpiry-[seconds] provides a slight offset, if token WILL expire in
// 10 seconds, obtain a new one
return (responseObj == null) || (tokenExpiry-offset) < (System.currentTimeMillis() / 1000);
}

private void setTokenExpiry(ResponseObj responseObj){
String[] parts = responseObj.getAccess_token().split("\\.");
JSONObject payload = new JSONObject(new String(Base64.getUrlDecoder().decode(parts[1])));
this.tokenExpiry = payload.getLong("exp");
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import ca.bc.gov.educ.api.gradbusiness.util.EducGradBusinessApiConstants;
import ca.bc.gov.educ.api.gradbusiness.util.EducGradBusinessUtil;
import ca.bc.gov.educ.api.gradbusiness.util.EducGraduationApiConstants;
import ca.bc.gov.educ.api.gradbusiness.util.TokenUtils;
import io.github.resilience4j.retry.annotation.Retry;
import jakarta.transaction.Transactional;
import org.apache.commons.lang3.StringUtils;
Expand All @@ -23,6 +24,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.CompletableFuture;

/**
* The type Grad business service.
Expand All @@ -41,26 +43,32 @@ public class GradBusinessService {
*/
final WebClient webClient;

/**
* Utility service to obtain access token
* */
final TokenUtils tokenUtils;

/**
* The Educ grad student api constants.
*/
@Autowired
EducGradBusinessApiConstants educGradStudentApiConstants;
final EducGradBusinessApiConstants educGradStudentApiConstants;

/**
* The Educ graduation api constants.
*/
@Autowired
EducGraduationApiConstants educGraduationApiConstants;
final EducGraduationApiConstants educGraduationApiConstants;

/**
* Instantiates a new Grad business service.
*
* @param webClient the web client
*/
@Autowired
public GradBusinessService(WebClient webClient) {
public GradBusinessService(WebClient webClient, TokenUtils tokenUtils, EducGradBusinessApiConstants educGradStudentApiConstants, EducGraduationApiConstants educGraduationApiConstants) {
this.webClient = webClient;
this.tokenUtils = tokenUtils;
this.educGradStudentApiConstants = educGradStudentApiConstants;
this.educGraduationApiConstants = educGraduationApiConstants;
}

/**
Expand Down Expand Up @@ -166,48 +174,6 @@ public ResponseEntity<byte[]> getStudentDemographicsByPen(String pen, String acc
}
}

/**
* Gets internal server error response.
*
* @param t the t
* @return the internal server error response
*/
protected ResponseEntity<byte[]> getInternalServerErrorResponse(Throwable t) {
ResponseEntity<byte[]> result;

Throwable tmp = t;
String message;
if (tmp.getCause() != null) {
tmp = tmp.getCause();
message = tmp.getMessage();
} else {
message = tmp.getMessage();
}
if(message == null) {
message = tmp.getClass().getName();
}

result = ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(message.getBytes());
return result;
}

private ResponseEntity<byte[]> handleBinaryResponse(byte[] resultBinary, String reportFile, MediaType contentType) {
ResponseEntity<byte[]> response;

if(resultBinary.length > 0) {
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Disposition", "inline; filename=" + reportFile);
response = ResponseEntity
.ok()
.headers(headers)
.contentType(contentType)
.body(resultBinary);
} else {
response = ResponseEntity.status(HttpStatus.NO_CONTENT).build();
}
return response;
}

public ResponseEntity<byte[]> getSchoolReportPDFByMincode(String mincode, String type, String accessToken) {
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("PST"), Locale.CANADA);
int year = cal.get(Calendar.YEAR);
Expand All @@ -229,29 +195,21 @@ public ResponseEntity<byte[]> getSchoolReportPDFByMincode(String mincode, String
}

public ResponseEntity<byte[]> getAmalgamatedSchoolReportPDFByMincode(String mincode, String type, String accessToken) {
logger.debug("******** Retrieve List of Students for Amalgamated School Report ******");
List<UUID> studentList = webClient.get().uri(String.format(educGradStudentApiConstants.getStudentsForAmalgamatedReport(), mincode, type)).headers(h -> h.setBearerAuth(accessToken)).retrieve().bodyToMono(new ParameterizedTypeReference<List<UUID>>() {
}).block();
logger.debug("******** Fetched Student List ******");
List<InputStream> locations = new ArrayList<>();
if (studentList != null && !studentList.isEmpty()) {
for (UUID studentID : studentList) {
InputStreamResource result = webClient.get().uri(String.format(educGraduationApiConstants.getStudentCredentialByType(), studentID, "ACHV")).headers(h -> h.setBearerAuth(accessToken)).retrieve().bodyToMono(InputStreamResource.class).block();
if (result != null) {
try {
locations.add(result.getInputStream());
} catch (IOException e) {
logger.debug("Error {}",e.getLocalizedMessage());
}
}
}
logger.debug("******** Fetched Achievement Reports ******");
logger.debug("******** Fetched {} students ******", studentList.size());
getStudentAchievementReports(studentList, locations);
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("PST"), Locale.CANADA);
int year = cal.get(Calendar.YEAR);
String month = "00";
String fileName = EducGradBusinessUtil.getFileNameSchoolReports(mincode, year, month, type);
try {
logger.debug("******** Merged Documents ******");
logger.debug("******** Merging Documents Started ******");
byte[] res = EducGradBusinessUtil.mergeDocuments(locations);
logger.debug("******** Merged {} Documents ******", locations.size());
HttpHeaders headers = new HttpHeaders();
headers.put(HttpHeaders.AUTHORIZATION, Collections.singletonList(BEARER + accessToken));
headers.put(HttpHeaders.ACCEPT, Collections.singletonList(APPLICATION_PDF));
Expand Down Expand Up @@ -284,7 +242,6 @@ public ResponseEntity<byte[]> getStudentCredentialPDFByType(String pen, String t
}
}


@Transactional
public ResponseEntity<byte[]> getStudentTranscriptPDFByType(String pen, String type, String interim, String accessToken) {
try {
Expand Down Expand Up @@ -316,4 +273,73 @@ public ResponseEntity<byte[]> getStudentTranscriptPDFByType(String pen, String t
return getInternalServerErrorResponse(e);
}
}

private void getStudentAchievementReports(List<UUID> studentList, List<InputStream> locations) {
logger.debug("******** Getting Student Achievement Reports ******");
List<CompletableFuture<InputStream>> futures = studentList.stream()
.map(studentGuid -> CompletableFuture.supplyAsync(() -> getStudentAchievementReport(studentGuid)))
.toList();
CompletableFuture<Void> allFutures = CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]));
CompletableFuture<List<InputStream>> result = allFutures.thenApply(v -> futures.stream()
.map(CompletableFuture::join)
.toList());
locations.addAll(result.join());
logger.debug("******** Fetched All Student Achievement Reports ******");
}

private InputStream getStudentAchievementReport(UUID studentGuid) {
String accessTokenNext = tokenUtils.getAccessToken();
InputStreamResource result = webClient.get().uri(String.format(educGraduationApiConstants.getStudentCredentialByType(), studentGuid, "ACHV")).headers(h -> h.setBearerAuth(accessTokenNext)).retrieve().bodyToMono(InputStreamResource.class).block();
if (result != null) {
try {
logger.debug("******** Fetched Achievement Report for {} ******", studentGuid);
return result.getInputStream();
} catch (IOException e) {
logger.debug("Error extracting report binary from stream: {}", e.getLocalizedMessage());
}
}
return null;
}

/**
* Gets internal server error response.
*
* @param t the t
* @return the internal server error response
*/
protected ResponseEntity<byte[]> getInternalServerErrorResponse(Throwable t) {
ResponseEntity<byte[]> result;

Throwable tmp = t;
String message;
if (tmp.getCause() != null) {
tmp = tmp.getCause();
message = tmp.getMessage();
} else {
message = tmp.getMessage();
}
if(message == null) {
message = tmp.getClass().getName();
}

result = ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(message.getBytes());
return result;
}

private ResponseEntity<byte[]> handleBinaryResponse(byte[] resultBinary, String reportFile, MediaType contentType) {
ResponseEntity<byte[]> response;

if(resultBinary.length > 0) {
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Disposition", "inline; filename=" + reportFile);
response = ResponseEntity
.ok()
.headers(headers)
.contentType(contentType)
.body(resultBinary);
} else {
response = ResponseEntity.status(HttpStatus.NO_CONTENT).build();
}
return response;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,15 @@ public class EducGraduationApiConstants {
@Value("${endpoint.grad-report-api.transcript-by-request.url}")
private String studentTranscriptReportByRequest;


@Value("${authorization.user}")
private String userName;

@Value("${authorization.password}")
private String password;

@Value("${endpoint.keycloak.getToken}")
private String tokenUrl;

@Value("${authorization.token-expiry-offset}")
private int tokenExpiryOffset;
}
Loading

0 comments on commit 0d3a98d

Please sign in to comment.