[Updated] Finished Frontend, Added statushandling to backend and frontend [Todo: session token handling at lvl 2]
This commit is contained in:
@@ -31,8 +31,13 @@ public class QuizController {
|
||||
@RequestParam(required = false) String type
|
||||
) {
|
||||
QuizSettings quizSettings = new QuizSettings(amount, category, difficulty, type);
|
||||
String jsonString = String.format("{\"sessionId\":\"%s\"}", quizService.createSession(quizSettings));
|
||||
return jsonString;
|
||||
Optional<String> result = quizService.createSession(quizSettings);
|
||||
if(result.isPresent()) {
|
||||
String jsonString = String.format("{\"statusCode\":0,\"sessionId\":\"%s\"}", result.get());
|
||||
return jsonString;
|
||||
}
|
||||
String jsonString = String.format("{\"statusCode\":5}");
|
||||
return jsonString;
|
||||
}
|
||||
|
||||
@GetMapping("/status/{sessionId}")
|
||||
|
||||
@@ -59,11 +59,6 @@ public class QuizSettings {
|
||||
|
||||
/** Inverse of toApiQuery */
|
||||
public QuizSettings(int amount, Integer category, String difficulty, String type) {
|
||||
System.out.println(amount);
|
||||
System.out.print(category);
|
||||
System.out.print(difficulty);
|
||||
System.out.print(type);
|
||||
|
||||
this.amount = amount;
|
||||
this.category = category == null ? Category.ANY : Category.values()[category];
|
||||
this.difficulty = difficulty == null ? Difficulty.ANY : Difficulty.valueOf(difficulty.toUpperCase());
|
||||
|
||||
@@ -1,21 +1,32 @@
|
||||
package com.ninjaws.quiz.services;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestClientException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import com.ninjaws.quiz.models.ApiResponse;
|
||||
import com.ninjaws.quiz.models.QuizSettings;
|
||||
import com.ninjaws.quiz.models.Request;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
@Service
|
||||
public class ApiService {
|
||||
|
||||
@Autowired
|
||||
private CleanerService cleanerService;
|
||||
@Autowired
|
||||
private ResponseHandlingService responseHandler;
|
||||
@Autowired
|
||||
private RestTemplate restTemplate;
|
||||
|
||||
private final String apiUrl = "https://opentdb.com/api.php";
|
||||
private static final System.Logger logger = System.getLogger(ApiService.class.getName());
|
||||
|
||||
private final String apiUrl = "https://opentdb.com/api.php";
|
||||
|
||||
public ApiService() {
|
||||
}
|
||||
@@ -24,25 +35,17 @@ public class ApiService {
|
||||
return apiUrl + quizSettings.toApiQuery();
|
||||
}
|
||||
|
||||
public String requestQuestions(@Valid QuizSettings quizSettings) {
|
||||
String url = buildRequestString(quizSettings);
|
||||
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
|
||||
String jsonData = response.getBody();
|
||||
|
||||
// try {
|
||||
// int responseCode = getResponseCode(jsonData);
|
||||
// /** When the code has expired or you've ran out of questions, update the token and try again */
|
||||
// if(responseCode == 3 || responseCode == 4) {
|
||||
// requestSessionToken();
|
||||
// requestQuestions(quizSettings);
|
||||
// }
|
||||
// } catch (Exception e) {
|
||||
// // Failed to get a responseCode, meaning the request failed completely (they always send a response_code)
|
||||
// }
|
||||
return jsonData;
|
||||
}
|
||||
|
||||
public void handleStatusCode(int statusCode) {
|
||||
public void handleRequest(@Valid Request request) {
|
||||
String url = buildRequestString(request.getQuizSettings());
|
||||
|
||||
try {
|
||||
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
|
||||
String json = response.getBody();
|
||||
ApiResponse apiResponse = cleanerService.extractApiResponse(json);
|
||||
responseHandler.handleResponse(request.getSessionId(), apiResponse);
|
||||
|
||||
} catch (RestClientException | IOException | IllegalStateException e) {
|
||||
logger.log(System.Logger.Level.ERROR, "Critical failure", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
package com.ninjaws.quiz.services;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.ninjaws.quiz.models.QuizSettings;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.ninjaws.quiz.models.ApiResponse;
|
||||
|
||||
@Service
|
||||
public class CleanerService {
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
public CleanerService() {}
|
||||
|
||||
public QuizSettings jsonToQuizSettings(String json){
|
||||
QuizSettings quizSettings = new QuizSettings();
|
||||
return quizSettings;
|
||||
public ApiResponse extractApiResponse(String json) throws IOException {
|
||||
return objectMapper.readValue(json, ApiResponse.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,10 +15,14 @@ public class QueueService {
|
||||
* Executes them in order, one every 5 seconds (the limit of the external API)
|
||||
* Removes them from the Queue when the request was succesful
|
||||
*/
|
||||
private final BlockingQueue<Request> requests = new LinkedBlockingQueue<>();
|
||||
private final BlockingQueue<Request> requests = new LinkedBlockingQueue<>(5);
|
||||
|
||||
public void addToQueue(Request request) {
|
||||
requests.add(request);
|
||||
/**
|
||||
* Offer automatically doesn't add it to the queue if it is full, so no Exceptions thrown
|
||||
* @return Whether it's possible to add the request to the queue
|
||||
*/
|
||||
public boolean addToQueue(Request request) {
|
||||
return requests.offer(request);
|
||||
}
|
||||
|
||||
public Request popFromQueue() {
|
||||
|
||||
@@ -27,12 +27,17 @@ public class QuizService {
|
||||
* @param quizSettings The values set by the user, determining the kind of quiz they want
|
||||
* @return The sessionId
|
||||
*/
|
||||
public String createSession(QuizSettings quizSettings) {
|
||||
public Optional<String> createSession(QuizSettings quizSettings) {
|
||||
Session session = new Session();
|
||||
cacheService.addToCache(session.getId(), session);
|
||||
queueService.addToQueue(new Request(session.getId(), quizSettings));
|
||||
boolean addedToQueue = queueService.addToQueue(new Request(session.getId(), quizSettings));
|
||||
/** In this order, because doing the queue before the cache could lead to race conditions (request completion immediately calls cache, but then the item doesn't exist) */
|
||||
if(!addedToQueue) {
|
||||
cacheService.removeFromCache(session.getId());
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return session.getId();
|
||||
return Optional.of(session.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,9 +54,16 @@ public class QuizService {
|
||||
throw new IllegalArgumentException("Invalid session ID: " + sessionId);
|
||||
}
|
||||
|
||||
/** Still waiting for a response */
|
||||
if (session.getQuestions() == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/** Anything other than statuscode 0 doesn't result in active sessions, so remove them from the cache after reporting to the frontend */
|
||||
if(session.getStatusCode() != 0) {
|
||||
cacheService.removeFromCache(session.getId());
|
||||
}
|
||||
|
||||
return Optional.of(session);
|
||||
}
|
||||
|
||||
@@ -76,10 +88,6 @@ public class QuizService {
|
||||
for(int i = 0; i < session.getQuestions().size(); i++) {
|
||||
String correctAnswer = session.getQuestions().get(i).getCorrectAnswer();
|
||||
String userAnswer = answers.getAnswers().get(i);
|
||||
// for(String answer : session.getQuestions().get(i).getAnswers()) {
|
||||
// System.out.print(answer);
|
||||
// }
|
||||
|
||||
if (correctAnswer.equalsIgnoreCase(userAnswer)) {
|
||||
amountCorrect++;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.ninjaws.quiz.services;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.ninjaws.quiz.models.ApiResponse;
|
||||
import com.ninjaws.quiz.models.Session;
|
||||
|
||||
@Service
|
||||
public class ResponseHandlingService {
|
||||
|
||||
@Autowired
|
||||
private CacheService cacheService;
|
||||
|
||||
/**
|
||||
* If there is no sessionId, then this is a request from the scheduler
|
||||
* purely to stock in the database No Cache call is required
|
||||
*/
|
||||
public void handleResponse(String sessionId, ApiResponse response) throws IllegalStateException {
|
||||
|
||||
Session session = cacheService.getFromCache(sessionId);
|
||||
if (session == null) {
|
||||
// Critical system failure: Should never occur
|
||||
throw new IllegalStateException("Session should be defined!");
|
||||
}
|
||||
|
||||
switch (response.getResponseCode()) {
|
||||
case 0:
|
||||
// Nothing
|
||||
updateSessionInCache(session, response);
|
||||
break;
|
||||
case 1:
|
||||
// Not enough questions to satisfy the request.
|
||||
updateSessionInCache(session, response);
|
||||
break;
|
||||
case 2:
|
||||
// Invalid parameters
|
||||
updateSessionInCache(session, response);
|
||||
break;
|
||||
case 3:
|
||||
// If it expired. Shouldn't happen since the backup database is full within 40 minutes, and the code lasts for 6 hours
|
||||
// Return a statuscode 1 to signal an issue, just in case a user hits this situation (should not happen)
|
||||
session.setStatusCode(1);
|
||||
updateSessionInCache(session, response);
|
||||
case 4:
|
||||
// It returns nothing, the previous batch was the last one
|
||||
// Return a statuscode 1 to the user
|
||||
session.setStatusCode(1);
|
||||
updateSessionInCache(session, response);
|
||||
break;
|
||||
case 5:
|
||||
// Should be prevented by the scheduler
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the request was made by the scheduler during queue-downtime, there is no session
|
||||
*/
|
||||
public void switchInternal(ApiResponse response) {
|
||||
switch (response.getResponseCode()) {
|
||||
case 0:
|
||||
// TODO: Store in DB
|
||||
break;
|
||||
case 4:
|
||||
// Signal DataService that we have extracted all the data. Stop gathering, switch to caching-mode
|
||||
// TODO: Signal DB that it's full, start db-swapping process
|
||||
break;
|
||||
case 5:
|
||||
// Should be prevented by the scheduler
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateSessionInCache(Session session, ApiResponse response) {
|
||||
session.setStatusCode(response.getResponseCode());
|
||||
session.setQuestions(response.getResults());
|
||||
cacheService.addToCache(session.getId(), session);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,17 +1,10 @@
|
||||
package com.ninjaws.quiz.services;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.ninjaws.quiz.models.ApiResponse;
|
||||
import com.ninjaws.quiz.models.Question;
|
||||
import com.ninjaws.quiz.models.Request;
|
||||
import com.ninjaws.quiz.models.Session;
|
||||
|
||||
@Service
|
||||
public class SchedulerService {
|
||||
@@ -20,40 +13,21 @@ public class SchedulerService {
|
||||
private QueueService queueService;
|
||||
@Autowired
|
||||
private ApiService apiService;
|
||||
@Autowired
|
||||
private CacheService cacheService;
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
private static final System.Logger logger = System.getLogger(SchedulerService.class.getName());
|
||||
|
||||
public SchedulerService() {}
|
||||
|
||||
/**
|
||||
* Every 5 seconds:
|
||||
* - Take the top request from the queue (removes it)
|
||||
* - Perform the request
|
||||
* - Wait for the data
|
||||
* - Find the matching session from the cache
|
||||
* - Add the data to the session in the cache
|
||||
* - Check if there is a request in queue
|
||||
* - Yes: Send it to the apiService
|
||||
* - No: Have the apiService do bulk gathering for the DB
|
||||
*/
|
||||
@Scheduled(fixedDelay = 5000)
|
||||
protected void processQueue() {
|
||||
Request request = queueService.popFromQueue();
|
||||
if (request != null) {
|
||||
String json = apiService.requestQuestions(request.getQuizSettings());
|
||||
try {
|
||||
List<Question> questions = extractQuestions(json);
|
||||
Session session = cacheService.getFromCache(request.getSessionId());
|
||||
session.setQuestions(questions);
|
||||
cacheService.addToCache(request.getSessionId(), session);
|
||||
} catch (IOException | NullPointerException e) {
|
||||
logger.log(System.Logger.Level.ERROR, "Critical failure", e);
|
||||
}
|
||||
apiService.handleRequest(request);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Question> extractQuestions(String json) throws IOException {
|
||||
return objectMapper.readValue(json, ApiResponse.class).getResults();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user