From 70a4e3213e4a1f52bf5a939a8a6ff5d27714b2ce Mon Sep 17 00:00:00 2001 From: juhi agarwal Date: Thu, 9 Feb 2023 15:27:55 +0530 Subject: [PATCH] Assessment Feature Related Changes (#172) Assessment feature merging to Release branch --- .../controller/AssessmentController.java | 10 +- .../assessment/repo/AssessmentRepository.java | 19 +- .../repo/AssessmentRepositoryImpl.java | 69 +- .../service/AssessmentServiceImpl.java | 6 +- .../service/AssessmentServiceV2.java | 2 + .../service/AssessmentServiceV2Impl.java | 925 ++++++++++++------ .../service/AssessmentUtilServiceV2.java | 8 + .../service/AssessmentUtilServiceV2Impl.java | 342 +++++-- .../common/util/CbExtServerProperties.java | 12 + .../org/sunbird/common/util/Constants.java | 53 +- 10 files changed, 1021 insertions(+), 425 deletions(-) diff --git a/src/main/java/org/sunbird/assessment/controller/AssessmentController.java b/src/main/java/org/sunbird/assessment/controller/AssessmentController.java index ec5b3bb08..c2bd3056c 100644 --- a/src/main/java/org/sunbird/assessment/controller/AssessmentController.java +++ b/src/main/java/org/sunbird/assessment/controller/AssessmentController.java @@ -147,6 +147,12 @@ public ResponseEntity readQuestionList(@Valid @RequestBody Map(response, response.getResponseCode()); } - // QUML based Assessment APIs - // ======================= + + @GetMapping("/v1/quml/assessment/retake/{assessmentIdentifier}") + public ResponseEntity retakeAssessment( + @PathVariable("assessmentIdentifier") String assessmentIdentifier, + @RequestHeader(Constants.X_AUTH_TOKEN) String token) throws Exception { + SBApiResponse readResponse = assessmentServiceV2.retakeAssessment(assessmentIdentifier, token); + return new ResponseEntity<>(readResponse, readResponse.getResponseCode()); + } } diff --git a/src/main/java/org/sunbird/assessment/repo/AssessmentRepository.java b/src/main/java/org/sunbird/assessment/repo/AssessmentRepository.java index 9895962df..62891dc70 100644 --- a/src/main/java/org/sunbird/assessment/repo/AssessmentRepository.java +++ b/src/main/java/org/sunbird/assessment/repo/AssessmentRepository.java @@ -11,7 +11,7 @@ public interface AssessmentRepository { /** * gets answer key for the assessment given the url - * + * * @param artifactUrl * @return * @throws Exception @@ -20,7 +20,7 @@ public interface AssessmentRepository { /** * gets answerkey for the quiz submission - * + * * @param quizMap * @return * @throws Exception @@ -29,7 +29,7 @@ public interface AssessmentRepository { /** * inserts quiz or assessments for a user - * + * * @param persist * @param isAssessment * @return @@ -40,7 +40,7 @@ public Map insertQuizOrAssessment(Map persist, B /** * gets assessment for a user given a content id - * + * * @param courseId * @param userId * @return @@ -49,7 +49,12 @@ public Map insertQuizOrAssessment(Map persist, B public List> getAssessmentbyContentUser(String rootOrg, String courseId, String userId) throws Exception; - boolean addUserAssesmentStartTime(String userId, String assessmentIdentifier, Timestamp startTime); + List> fetchUserAssessmentDataFromDB(String userId, String assessmentIdentifier); - Date fetchUserAssessmentStartTime(String userId, String s); -} + boolean addUserAssesmentDataToDB(String userId, String assessmentId, Timestamp startTime, Timestamp endTime, + Map questionSet, String status); + + Boolean updateUserAssesmentDataToDB(String userId, String assessmentIdentifier, + Map submitAssessmentRequest, Map submitAssessmentResponse, String status, + Date startTime); +} \ No newline at end of file diff --git a/src/main/java/org/sunbird/assessment/repo/AssessmentRepositoryImpl.java b/src/main/java/org/sunbird/assessment/repo/AssessmentRepositoryImpl.java index f3575b006..7092c5a56 100644 --- a/src/main/java/org/sunbird/assessment/repo/AssessmentRepositoryImpl.java +++ b/src/main/java/org/sunbird/assessment/repo/AssessmentRepositoryImpl.java @@ -1,6 +1,14 @@ package org.sunbird.assessment.repo; -import com.datastax.driver.core.utils.UUIDs; +import java.math.BigDecimal; +import java.sql.Timestamp; +import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.sunbird.assessment.dto.AssessmentSubmissionDTO; @@ -9,10 +17,8 @@ import org.sunbird.common.util.Constants; import org.sunbird.core.logger.CbExtLogger; -import java.math.BigDecimal; -import java.sql.Timestamp; -import java.text.SimpleDateFormat; -import java.util.*; +import com.datastax.driver.core.utils.UUIDs; +import com.google.gson.Gson; @Service public class AssessmentRepositoryImpl implements AssessmentRepository { @@ -22,7 +28,7 @@ public class AssessmentRepositoryImpl implements AssessmentRepository { public static final String SOURCE_ID = "sourceId"; public static final String USER_ID = "userId"; private CbExtLogger logger = new CbExtLogger(getClass().getName()); - + @Autowired UserAssessmentSummaryRepository userAssessmentSummaryRepo; @@ -128,23 +134,50 @@ public List> getAssessmentbyContentUser(String rootOrg, Stri } @Override - public boolean addUserAssesmentStartTime(String userId, String assessmentIdentifier, Timestamp startTime) { + public boolean addUserAssesmentDataToDB(String userId, String assessmentIdentifier, Timestamp startTime, + Timestamp endTime, Map questionSet, String status) { Map request = new HashMap<>(); request.put(Constants.USER_ID, userId); - request.put(Constants.IDENTIFIER, assessmentIdentifier); - cassandraOperation.deleteRecord(Constants.KEYSPACE_SUNBIRD, Constants.TABLE_USER_ASSESSMENT_TIME, request); - request.put("starttime", startTime); - SBApiResponse resp = cassandraOperation.insertRecord(Constants.KEYSPACE_SUNBIRD, Constants.TABLE_USER_ASSESSMENT_TIME, request); + request.put(Constants.ASSESSMENT_ID_KEY, assessmentIdentifier); + request.put(Constants.START_TIME, startTime); + request.put(Constants.END_TIME, endTime); + request.put(Constants.ASSESSMENT_READ_RESPONSE, new Gson().toJson(questionSet)); + request.put(Constants.STATUS, status); + SBApiResponse resp = cassandraOperation.insertRecord(Constants.KEYSPACE_SUNBIRD, + Constants.TABLE_USER_ASSESSMENT_DATA, request); return resp.get(Constants.RESPONSE).equals(Constants.SUCCESS); } - @Override - public Date fetchUserAssessmentStartTime(String userId, String assessmentIdentifier) { + @Override + public List> fetchUserAssessmentDataFromDB(String userId, String assessmentIdentifier) { Map request = new HashMap<>(); request.put(Constants.USER_ID, userId); - request.put(Constants.IDENTIFIER, assessmentIdentifier); - Map existingDataList = cassandraOperation.getRecordsByProperties(Constants.KEYSPACE_SUNBIRD, - Constants.TABLE_USER_ASSESSMENT_TIME, request, null).get(0); - return (Date) existingDataList.get("starttime"); + request.put(Constants.ASSESSMENT_ID_KEY, assessmentIdentifier); + List> existingDataList = cassandraOperation.getRecordsByProperties( + Constants.KEYSPACE_SUNBIRD, Constants.TABLE_USER_ASSESSMENT_DATA, request, null); + return existingDataList; + } + + @Override + public Boolean updateUserAssesmentDataToDB(String userId, String assessmentIdentifier, + Map submitAssessmentRequest, Map submitAssessmentResponse, String status, + Date startTime) { + Map compositeKeys = new HashMap<>(); + compositeKeys.put(Constants.USER_ID, userId); + compositeKeys.put(Constants.ASSESSMENT_ID_KEY, assessmentIdentifier); + compositeKeys.put(Constants.START_TIME, startTime); + Map fieldsToBeUpdated = new HashMap<>(); + if (!submitAssessmentRequest.isEmpty()) { + fieldsToBeUpdated.put("submitassessmentrequest", new Gson().toJson(submitAssessmentRequest)); + } + if (!submitAssessmentResponse.isEmpty()) { + fieldsToBeUpdated.put("submitassessmentresponse", new Gson().toJson(submitAssessmentResponse)); + } + if (!status.isEmpty()) { + fieldsToBeUpdated.put(Constants.STATUS, status); + } + cassandraOperation.updateRecord(Constants.KEYSPACE_SUNBIRD, Constants.TABLE_USER_ASSESSMENT_DATA, + fieldsToBeUpdated, compositeKeys); + return true; } -} +} \ No newline at end of file diff --git a/src/main/java/org/sunbird/assessment/service/AssessmentServiceImpl.java b/src/main/java/org/sunbird/assessment/service/AssessmentServiceImpl.java index 2fe1e5c48..d12bd50b6 100644 --- a/src/main/java/org/sunbird/assessment/service/AssessmentServiceImpl.java +++ b/src/main/java/org/sunbird/assessment/service/AssessmentServiceImpl.java @@ -84,7 +84,7 @@ public Map submitAssessment(String rootOrg, AssessmentSubmission // Fetch parent of an assessment with status live String parentId = contentService.getParentIdentifier(data.getIdentifier()); - + persist.put("parent", parentId); persist.put(RESULT, result); persist.put("sourceId", data.getIdentifier()); @@ -228,7 +228,7 @@ public Map getAssessmentContent(String courseId, String assessme && child.getArtifactUrl().endsWith(".json")) { // read assessment json file QuestionSet assessmentContent = mapper.convertValue(outboundRequestHandlerService - .fetchUsingGetWithHeaders(child.getArtifactUrl(), new HashMap<>()), + .fetchUsingGetWithHeaders(child.getArtifactUrl(), new HashMap<>()), QuestionSet.class); QuestionSet assessmentQnsSet = assessUtilServ.removeAssessmentAnsKey(assessmentContent); @@ -255,4 +255,4 @@ public Map getAssessmentContent(String courseId, String assessme return result; } -} +} \ No newline at end of file diff --git a/src/main/java/org/sunbird/assessment/service/AssessmentServiceV2.java b/src/main/java/org/sunbird/assessment/service/AssessmentServiceV2.java index 84a9b3ec0..e6159dbfd 100644 --- a/src/main/java/org/sunbird/assessment/service/AssessmentServiceV2.java +++ b/src/main/java/org/sunbird/assessment/service/AssessmentServiceV2.java @@ -17,4 +17,6 @@ public interface AssessmentServiceV2 { public SBApiResponse readAssessment(String assessmentIdentifier, String token) throws Exception; public SBApiResponse readQuestionList(Map requestBody, String authUserToken) throws Exception; + + public SBApiResponse retakeAssessment(String assessmentIdentifier, String token) throws Exception; } diff --git a/src/main/java/org/sunbird/assessment/service/AssessmentServiceV2Impl.java b/src/main/java/org/sunbird/assessment/service/AssessmentServiceV2Impl.java index 4c1377115..badf33a1e 100644 --- a/src/main/java/org/sunbird/assessment/service/AssessmentServiceV2Impl.java +++ b/src/main/java/org/sunbird/assessment/service/AssessmentServiceV2Impl.java @@ -1,17 +1,18 @@ package org.sunbird.assessment.service; -import java.sql.Timestamp; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.stream.Collectors; - +import com.beust.jcommander.internal.Lists; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.base.Predicates; +import com.google.common.collect.Iterables; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang.StringUtils; +import org.joda.time.DateTime; +import org.mortbay.util.ajax.JSON; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; @@ -19,213 +20,590 @@ import org.sunbird.assessment.repo.AssessmentRepository; import org.sunbird.cache.RedisCacheMgr; import org.sunbird.common.model.SBApiResponse; -import org.sunbird.common.model.SunbirdApiResp; import org.sunbird.common.service.OutboundRequestHandlerServiceImpl; import org.sunbird.common.util.CbExtServerProperties; import org.sunbird.common.util.Constants; import org.sunbird.common.util.RequestInterceptor; -import org.sunbird.core.exception.ApplicationLogicError; -import org.sunbird.core.logger.CbExtLogger; +import org.sunbird.core.producer.Producer; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.*; +import java.io.IOException; +import java.sql.Timestamp; +import java.util.stream.Collectors; + +import static java.util.stream.Collectors.toList; @Service @SuppressWarnings("unchecked") public class AssessmentServiceV2Impl implements AssessmentServiceV2 { - private final CbExtLogger logger = new CbExtLogger(getClass().getName()); - private final ObjectMapper mapper = new ObjectMapper(); + private final Logger logger = LoggerFactory.getLogger(AssessmentServiceV2Impl.class); @Autowired AssessmentUtilServiceV2 assessUtilServ; @Autowired - RedisCacheMgr redisCacheMgr; + CbExtServerProperties serverProperties; @Autowired - CbExtServerProperties cbExtServerProperties; + Producer kafkaProducer; + + @Autowired + AssessmentRepository assessmentRepository; + + @Autowired + RedisCacheMgr redisCacheMgr; @Autowired OutboundRequestHandlerServiceImpl outboundRequestHandlerService; @Autowired - AssessmentRepository assessmentRepository; + ObjectMapper mapper; + + public SBApiResponse readAssessment(String assessmentIdentifier, String token) { + logger.info("AssessmentServiceV2Impl::readAssessment... Started"); + SBApiResponse response = createDefaultResponse(Constants.API_QUESTIONSET_HIERARCHY_GET); + String errMsg; + try { + String userId = validateAuthTokenAndFetchUserId(token); + if (userId != null) { + logger.info("readAssessment.. userId :" + userId); + Map assessmentAllDetail = new HashMap<>(); + errMsg = fetchReadHierarchyDetails(assessmentAllDetail, token, assessmentIdentifier); + if (errMsg.isEmpty() && !((String) assessmentAllDetail.get(Constants.PRIMARY_CATEGORY)).equalsIgnoreCase(Constants.PRACTICE_QUESTION_SET)) { + logger.info("Fetched assessment Details... for : " + assessmentIdentifier); + List> existingDataList = assessmentRepository.fetchUserAssessmentDataFromDB(userId, assessmentIdentifier); + Timestamp assessmentStartTime = new Timestamp(new java.util.Date().getTime()); + if (existingDataList.isEmpty()) { + int expectedDuration = (Integer) assessmentAllDetail.get(Constants.EXPECTED_DURATION); + Timestamp assessmentEndTime = calculateAssessmentSubmitTime(expectedDuration, assessmentStartTime, 0); + logger.info("Assessment read first time for user."); + Map assessmentData = readAssessmentLevelData(assessmentAllDetail); + assessmentData.put(Constants.START_TIME, assessmentStartTime.getTime()); + assessmentData.put(Constants.END_TIME, assessmentEndTime.getTime()); + response.getResult().put(Constants.QUESTION_SET, assessmentData); + redisCacheMgr.putCache(Constants.USER_ASSESS_REQ + assessmentIdentifier + "_" + token, response.getResult().get(Constants.QUESTION_SET)); + Boolean isAssessmentUpdatedToDB = assessmentRepository.addUserAssesmentDataToDB(userId, assessmentIdentifier, assessmentStartTime, assessmentEndTime, (Map) (response.getResult().get(Constants.QUESTION_SET)), Constants.NOT_SUBMITTED); + if (Boolean.FALSE.equals(isAssessmentUpdatedToDB)) { + errMsg = Constants.ASSESSMENT_DATA_START_TIME_NOT_UPDATED; + } + } else { + logger.info("Assessment read... user has details... "); + java.util.Date existingAssessmentEndTime = (java.util.Date) (existingDataList.get(0).get(Constants.END_TIME)); + Timestamp existingAssessmentEndTimeTimestamp = new Timestamp(existingAssessmentEndTime.getTime()); + if (assessmentStartTime.compareTo(existingAssessmentEndTimeTimestamp) < 0 && ((String) existingDataList.get(0).get(Constants.STATUS)).equalsIgnoreCase(Constants.NOT_SUBMITTED)) { + Map questionSetFromAssessment; + String userQuestionSet = redisCacheMgr.getCache(Constants.USER_ASSESS_REQ + assessmentIdentifier + "_" + token); + if (!ObjectUtils.isEmpty(userQuestionSet)) { + questionSetFromAssessment = mapper.readValue(userQuestionSet, new TypeReference>() { + }); + } else { + String questionSetFromAssessmentString = (String) existingDataList.get(0).get(Constants.ASSESSMENT_READ_RESPONSE); + questionSetFromAssessment = new Gson().fromJson(questionSetFromAssessmentString, new TypeToken>() { + }.getType()); + questionSetFromAssessment.put(Constants.START_TIME, assessmentStartTime.getTime()); + questionSetFromAssessment.put(Constants.END_TIME, existingAssessmentEndTimeTimestamp.getTime()); + response.getResult().put(Constants.QUESTION_SET, questionSetFromAssessment); + redisCacheMgr.putCache(Constants.USER_ASSESS_REQ + assessmentIdentifier + "_" + token, questionSetFromAssessment); + } + response.getResult().put(Constants.QUESTION_SET, questionSetFromAssessment); + } else if ((assessmentStartTime.compareTo(existingAssessmentEndTime) < 0 && ((String) existingDataList.get(0).get(Constants.STATUS)).equalsIgnoreCase(Constants.SUBMITTED)) || assessmentStartTime.compareTo(existingAssessmentEndTime) > 0) { + logger.info("Incase the assessment is submitted before the end time, or the endtime has exceeded, read assessment freshly "); + Map assessmentData = readAssessmentLevelData(assessmentAllDetail); + int expectedDuration = (Integer) assessmentAllDetail.get(Constants.EXPECTED_DURATION); + assessmentStartTime = new Timestamp(new java.util.Date().getTime()); + Timestamp assessmentEndTime = calculateAssessmentSubmitTime(expectedDuration, assessmentStartTime, 0); + assessmentData.put(Constants.START_TIME, assessmentStartTime.getTime()); + assessmentData.put(Constants.END_TIME, assessmentEndTime.getTime()); + response.getResult().put(Constants.QUESTION_SET, assessmentData); + Boolean isAssessmentUpdatedToDB = assessmentRepository.addUserAssesmentDataToDB(userId, assessmentIdentifier, assessmentStartTime, calculateAssessmentSubmitTime(expectedDuration, assessmentStartTime, 0), (Map) (response.getResult().get(Constants.QUESTION_SET)), Constants.NOT_SUBMITTED); + redisCacheMgr.putCache(Constants.USER_ASSESS_REQ + assessmentIdentifier + "_" + token, response.getResult().get(Constants.QUESTION_SET)); + if (Boolean.FALSE.equals(isAssessmentUpdatedToDB)) { + errMsg = Constants.ASSESSMENT_DATA_START_TIME_NOT_UPDATED; + } + } + } + } else if (errMsg.isEmpty() && ((String) assessmentAllDetail.get(Constants.PRIMARY_CATEGORY)).equalsIgnoreCase(Constants.PRACTICE_QUESTION_SET)) { + response.getResult().put(Constants.QUESTION_SET, readAssessmentLevelData(assessmentAllDetail)); + redisCacheMgr.putCache(Constants.USER_ASSESS_REQ + assessmentIdentifier + "_" + token, response.getResult().get(Constants.QUESTION_SET)); + } + } else { + errMsg = Constants.USER_ID_DOESNT_EXIST; + } + } catch (Exception e) { + logger.error(String.format("Exception in %s : %s", "read Assessment", e.getMessage()), e); + errMsg = "Failed to read Assessment. Exception: " + e.getMessage(); + } + if (StringUtils.isNotBlank(errMsg)) { + response.getParams().setStatus(Constants.FAILED); + response.getParams().setErrmsg(errMsg); + response.setResponseCode(HttpStatus.INTERNAL_SERVER_ERROR); + } + return response; + } - public SBApiResponse readAssessment(String assessmentIdentifier, String token) throws Exception { - SBApiResponse response = new SBApiResponse(); - try { - String userId = RequestInterceptor.fetchUserIdFromAccessToken(token); - if (userId != null) { - String strAssessmentAllDetail = redisCacheMgr.getCache(Constants.ASSESSMENT_ID + assessmentIdentifier); - - Map assessmentAllDetail = mapper.readValue(strAssessmentAllDetail, - new TypeReference>() { - }); - boolean isSuccess = true; - if (ObjectUtils.isEmpty(assessmentAllDetail)) { - Map hierarcyReadApiResponse = getReadHierarchyApiResponse(assessmentIdentifier, - token); - if (!Constants.OK.equalsIgnoreCase((String) hierarcyReadApiResponse.get(Constants.RESPONSE_CODE))) { - isSuccess = false; - } else { - assessmentAllDetail = (Map) ((Map) hierarcyReadApiResponse - .get(Constants.RESULT)).get(Constants.QUESTION_SET); - redisCacheMgr.putCache(Constants.ASSESSMENT_ID + assessmentIdentifier, assessmentAllDetail); - } - } - response = prepareAssessmentResponse(assessmentAllDetail, isSuccess); - redisCacheMgr.putCache(Constants.USER_ASSESS_REQ + token, - response.getResult().get(Constants.QUESTION_SET)); - if (assessmentAllDetail.get(Constants.DURATION) != null) { - boolean resp = assessmentRepository.addUserAssesmentStartTime(userId, - Constants.ASSESSMENT_ID + assessmentIdentifier, new Timestamp(new Date().getTime())); - return response; - } - } - } catch (Exception e) { - logger.error(e); - throw new ApplicationLogicError("REQUEST_COULD_NOT_BE_PROCESSED", e); - } - return response; - } - - public SBApiResponse readQuestionList(Map requestBody, String authUserToken) throws Exception { + public SBApiResponse readQuestionList(Map requestBody, String authUserToken) { + SBApiResponse response = createDefaultResponse(Constants.API_SUBMIT_ASSESSMENT); + String errMsg; + String primaryCategory = ""; + Map result = new HashMap<>(); try { - List identifierList = getQuestionIdList(requestBody); + List identifierList = new ArrayList<>(); List questionList = new ArrayList<>(); - List newIdentifierList = new ArrayList<>(); - List strQuestionList = redisCacheMgr.mget(identifierList); - - int size = strQuestionList.size(); - for (int i = 0; i < size; i++) { - if (ObjectUtils.isEmpty(strQuestionList.get(i))) { - newIdentifierList.add(identifierList.get(i)); - } else { - Map questionMap = mapper.readValue(strQuestionList.get(i), - new TypeReference>() { - }); - questionList.add(filterQuestionMapDetail(questionMap)); - } - } - if (newIdentifierList.size() > 0) { - Map questionMapResponse = readQuestionDetails(newIdentifierList); - if (questionMapResponse != null && Constants.OK.equalsIgnoreCase((String) questionMapResponse.get(Constants.RESPONSE_CODE))) { - List> questionMap = ((List>) ((Map) questionMapResponse - .get(Constants.RESULT)).get(Constants.QUESTIONS)); - for (Map qmap : questionMap) { - if (!ObjectUtils.isEmpty(questionMap)) { - redisCacheMgr.putCache(Constants.QUESTION_ID + qmap.get(Constants.IDENTIFIER), qmap); - questionList.add(filterQuestionMapDetail(qmap)); - } else { - logger.error(new Exception("Failed to get Question Details for Id: " + qmap.get(Constants.IDENTIFIER))); + result = validateQuestionListAPI(requestBody, authUserToken, identifierList); + errMsg = result.get(Constants.ERROR_MESSAGE); + if (errMsg.isEmpty()) { + List newIdentifierList = new ArrayList<>(); + List map = redisCacheMgr.mget(identifierList); + for (int i = 0; i < map.size(); i++) { + if (ObjectUtils.isEmpty(map.get(i))) { + newIdentifierList.add(identifierList.get(i)); + } else { + Map questionString = mapper.readValue(map.get(i), new TypeReference>() { + }); + questionList.add(assessUtilServ.filterQuestionMapDetail(questionString, result.get(Constants.PRIMARY_CATEGORY))); + } + } + if (!newIdentifierList.isEmpty()) { + List> newQuestionList = assessUtilServ.readQuestionDetails(newIdentifierList); + if (!newQuestionList.isEmpty()) { + for (Map questionMap : newQuestionList) { + if (!ObjectUtils.isEmpty(questionMap) && !ObjectUtils.isEmpty(((Map) questionMap.get(Constants.RESULT)).get(Constants.QUESTIONS))) { + List> questions = (List>) ((Map) questionMap.get(Constants.RESULT)).get(Constants.QUESTIONS); + for (Map question : questions) { + if (!question.isEmpty()) { + redisCacheMgr.putCache(Constants.QUESTION_ID + question.get(Constants.IDENTIFIER), question); + questionList.add(assessUtilServ.filterQuestionMapDetail(question, result.get(Constants.PRIMARY_CATEGORY))); + } + } + } } + } else { + errMsg = Constants.FAILED_TO_GET_QUESTION_DETAILS; + logger.error(String.valueOf(new Exception("Failed to get Question Details for Ids"))); } } + if (errMsg.isEmpty() && identifierList.size() == questionList.size()) { + response.getResult().put(Constants.QUESTIONS, questionList); + } + } + } catch (Exception e) { + logger.error(String.format("Exception in %s : %s", "get Question List", e.getMessage()), e); + errMsg = "Failed to fetch the question list. Exception: " + e.getMessage(); + } + if (StringUtils.isNotBlank(errMsg)) { + response.getParams().setStatus(Constants.FAILED); + response.getParams().setErrmsg(errMsg); + response.setResponseCode(HttpStatus.BAD_REQUEST); + } + return response; + + } + + private String validateAuthTokenAndFetchUserId(String authUserToken) { + return RequestInterceptor.fetchUserIdFromAccessToken(authUserToken); + } + + private String fetchReadHierarchyDetails(Map assessmentAllDetail, String token, String assessmentIdentifier) throws IOException { + try { + String assessmentData = redisCacheMgr.getCache(Constants.ASSESSMENT_ID + assessmentIdentifier); + logger.info("Reading assessmentData from redis" + assessmentData); + if (!ObjectUtils.isEmpty(assessmentData)) { + assessmentAllDetail.putAll(mapper.readValue(assessmentData, new TypeReference>() { + })); + } else { + Map readHierarchyApiResponse = assessUtilServ.getReadHierarchyApiResponse(assessmentIdentifier, token); + if (!readHierarchyApiResponse.isEmpty()) + if (ObjectUtils.isEmpty(readHierarchyApiResponse) || !Constants.OK.equalsIgnoreCase((String) readHierarchyApiResponse.get(Constants.RESPONSE_CODE))) { + return Constants.ASSESSMENT_HIERARCHY_READ_FAILED; + } + assessmentAllDetail.putAll((Map) ((Map) readHierarchyApiResponse.get(Constants.RESULT)).get(Constants.QUESTION_SET)); + redisCacheMgr.putCache(Constants.ASSESSMENT_ID + assessmentIdentifier, ((Map) readHierarchyApiResponse.get(Constants.RESULT)).get(Constants.QUESTION_SET)); } - return prepareQuestionResponse(questionList, questionList.size() > 0); } catch (Exception e) { - logger.error(e); - throw new ApplicationLogicError("REQUEST_COULD_NOT_BE_PROCESSED", e); + logger.info("Error while fetching or mapping read hierarchy data" + e.getMessage()); + return Constants.ASSESSMENT_HIERARCHY_READ_FAILED; } + return StringUtils.EMPTY; + } + private Map validateQuestionListAPI(Map requestBody, String authUserToken, List identifierList) throws IOException { + Map result = new HashMap<>(); + String userId = validateAuthTokenAndFetchUserId(authUserToken); + if (StringUtils.isBlank(userId)) { + result.put(Constants.ERROR_MESSAGE, Constants.USER_ID_DOESNT_EXIST); + return result; + } + String assessmentIdFromRequest = (String) requestBody.get(Constants.ASSESSMENT_ID_KEY); + if (StringUtils.isBlank(assessmentIdFromRequest)) { + result.put(Constants.ERROR_MESSAGE, Constants.ASSESSMENT_ID_KEY_IS_NOT_PRESENT_IS_EMPTY); + return result; + } + identifierList.addAll(getQuestionIdList(requestBody)); + if (identifierList.isEmpty()) { + result.put(Constants.ERROR_MESSAGE, Constants.IDENTIFIER_LIST_IS_EMPTY); + return result; + } + Map assessmentAllDetail = new HashMap<>(); + String errMsg = fetchReadHierarchyDetails(assessmentAllDetail, authUserToken, assessmentIdFromRequest); + Map userAssessmentAllDetail; + if (errMsg.isEmpty()) { + userAssessmentAllDetail = new HashMap<>(); + String userQuestionSet = redisCacheMgr.getCache(Constants.USER_ASSESS_REQ + assessmentIdFromRequest + "_" + authUserToken); + if (!ObjectUtils.isEmpty(userQuestionSet)) { + userAssessmentAllDetail.putAll(mapper.readValue(userQuestionSet, new TypeReference>() { + })); + } else if (ObjectUtils.isEmpty(userQuestionSet)) { + if (!((String) assessmentAllDetail.get(Constants.PRIMARY_CATEGORY)).equalsIgnoreCase(Constants.PRACTICE_QUESTION_SET)) { + List> existingDataList = assessmentRepository.fetchUserAssessmentDataFromDB(userId, assessmentIdFromRequest); + String questionSetFromAssessmentString = (!existingDataList.isEmpty()) ? (String) existingDataList.get(0).get(Constants.ASSESSMENT_READ_RESPONSE) : ""; + if (!questionSetFromAssessmentString.isEmpty()) { + userAssessmentAllDetail.putAll(new Gson().fromJson(questionSetFromAssessmentString, new TypeToken>() { + }.getType())); + } else { + result.put(Constants.ERROR_MESSAGE, Constants.USER_ASSESSMENT_DATA_NOT_PRESENT); + return result; + } + } + } else { + result.put(Constants.ERROR_MESSAGE, Constants.ASSESSMENT_ID_INVALID_SESSION_EXPIRED); + return result; + } + } else { + result.put(Constants.ERROR_MESSAGE, errMsg); + return result; + } + String assessmentIdFromDatabase = (String) (userAssessmentAllDetail.get(Constants.IDENTIFIER)); + if (assessmentIdFromDatabase.equalsIgnoreCase(assessmentIdFromRequest)) { + result.put(Constants.PRIMARY_CATEGORY, (String) userAssessmentAllDetail.get(Constants.PRIMARY_CATEGORY)); + List questionsFromAssessment = new ArrayList<>(); + List> sections = (List>) userAssessmentAllDetail.get(Constants.CHILDREN); + for (Map section : sections) { + questionsFromAssessment.addAll((List) section.get(Constants.CHILD_NODES)); + // Out of the list of questions received in the payload, checking if the request + // has only those ids which are a part of the user's latest assessment + // Fetching all the remaining questions details from the Redis + if (Boolean.FALSE.equals(validateQuestionListRequest(identifierList, questionsFromAssessment))) { + result.put(Constants.ERROR_MESSAGE, Constants.THE_QUESTIONS_IDS_PROVIDED_DONT_MATCH); + return result; + } + } + result.put(Constants.ERROR_MESSAGE, ""); + return result; + } else { + result.put(Constants.ERROR_MESSAGE, Constants.ASSESSMENT_ID_INVALID); + return result; + } } @Override - public SBApiResponse submitAssessment(Map data, String authUserToken) throws Exception { - SBApiResponse outgoingResponse = new SBApiResponse(); - String assessmentId = (String) data.get(Constants.IDENTIFIER); - String strAssessmentHierarchy = redisCacheMgr - .getCache(Constants.ASSESSMENT_ID + assessmentId); - - Map assessmentHierarchy = mapper.readValue(strAssessmentHierarchy, - new TypeReference>() { - }); - - String userId = RequestInterceptor.fetchUserIdFromAccessToken(authUserToken); - if (userId != null) { - Date assessmentStartTime = assessmentRepository.fetchUserAssessmentStartTime(userId, Constants.ASSESSMENT_ID + assessmentId); - if (assessmentStartTime != null) { - Timestamp submissionTime = new Timestamp(new Date().getTime()); - Calendar cal = Calendar.getInstance(); - cal.setTimeInMillis(new Timestamp(assessmentStartTime.getTime()).getTime()); - cal.add(Calendar.SECOND, Integer.valueOf((String) assessmentHierarchy.get(Constants.DURATION)).intValue() + Integer.valueOf(cbExtServerProperties.getUserAssessmentSubmissionDuration())); - Timestamp later = new Timestamp(cal.getTime().getTime()); - int time = submissionTime.compareTo(later); - if (time <= 0) { - outgoingResponse.setResponseCode(HttpStatus.OK); - outgoingResponse.getResult().put(Constants.IDENTIFIER, assessmentId); - outgoingResponse.getResult().put(Constants.OBJECT_TYPE, assessmentHierarchy.get(Constants.OBJECT_TYPE)); - outgoingResponse.getResult().put(Constants.PRIMARY_CATEGORY, assessmentHierarchy.get(Constants.PRIMARY_CATEGORY)); - - // Check Sections are available - if (data.containsKey(Constants.CHILDREN) - && !CollectionUtils.isEmpty((List>) data.get(Constants.CHILDREN))) { - List> sectionList = (List>) data.get(Constants.CHILDREN); - - for (Map section : sectionList) { - String id = (String) section.get(Constants.IDENTIFIER); - String scoreCutOffType = ((String) section.get(Constants.SCORE_CUTOFF_TYPE)).toLowerCase(); - switch (scoreCutOffType) { - case Constants.ASSESSMENT_LEVEL_SCORE_CUTOFF: { - if (sectionList.size() > 1) { - // There should be only one section -- if not -- throw error - } - validateAssessmentLevelScore(outgoingResponse, section, assessmentHierarchy); - } - break; - case Constants.SECTION_LEVEL_SCORE_CUTOFF: { - } + public SBApiResponse submitAssessment(Map submitRequest, String authUserToken) throws IOException { + SBApiResponse outgoingResponse = createDefaultResponse(Constants.API_SUBMIT_ASSESSMENT); + String errMsg; + List> sectionListFromSubmitRequest = new ArrayList<>(); + List> hierarchySectionList = new ArrayList<>(); + List questionsListFromAssessmentHierarchy = new ArrayList<>(); + Map assessmentHierarchy = new HashMap<>(); + errMsg = validateSubmitAssessmentRequest(submitRequest, authUserToken, hierarchySectionList, sectionListFromSubmitRequest, assessmentHierarchy); + if (errMsg.isEmpty()) { + String userId = validateAuthTokenAndFetchUserId(authUserToken); + String scoreCutOffType = ((String) assessmentHierarchy.get(Constants.SCORE_CUTOFF_TYPE)).toLowerCase(); + List> existingDataList = new ArrayList<>(); + List> sectionLevelsResults = new ArrayList<>(); + Map questionSetFromAssessment = new HashMap<>(); + for (Map hierarchySection : hierarchySectionList) { + String hierarchySectionId = (String) hierarchySection.get(Constants.IDENTIFIER); + String userSectionId = ""; + Map userSectionData = new HashMap<>(); + for (Map sectionFromSubmitRequest : sectionListFromSubmitRequest) { + userSectionId = (String) sectionFromSubmitRequest.get(Constants.IDENTIFIER); + if (userSectionId.equalsIgnoreCase(hierarchySectionId)) { + userSectionData = sectionFromSubmitRequest; + break; + } + } + if (!((String) (assessmentHierarchy.get(Constants.PRIMARY_CATEGORY))).equalsIgnoreCase(Constants.PRACTICE_QUESTION_SET)) { + String assessmentData = redisCacheMgr.getCache(Constants.USER_ASSESS_REQ + submitRequest.get(Constants.IDENTIFIER) + "_" + authUserToken); + if (!ObjectUtils.isEmpty(assessmentData)) { + questionSetFromAssessment.putAll(mapper.readValue(assessmentData, new TypeReference>() { + })); + } else { + existingDataList = assessmentRepository.fetchUserAssessmentDataFromDB(userId, (String) submitRequest.get(Constants.IDENTIFIER)); + String questionSetFromAssessmentString = (!existingDataList.isEmpty()) ? (String) existingDataList.get(0).get(Constants.ASSESSMENT_READ_RESPONSE) : ""; + if (!questionSetFromAssessmentString.isEmpty()) { + questionSetFromAssessment = new Gson().fromJson(questionSetFromAssessmentString, new TypeToken>() { + }.getType()); + } + } + if (questionSetFromAssessment != null && questionSetFromAssessment.get(Constants.CHILDREN) != null) { + List> sections = (List>) questionSetFromAssessment.get(Constants.CHILDREN); + for (Map section : sections) { + String sectionId = (String) section.get(Constants.IDENTIFIER); + if (userSectionId.equalsIgnoreCase(sectionId)) { + questionsListFromAssessmentHierarchy = (List) section.get(Constants.CHILD_NODES); break; - default: - break; } } } else { - // TODO - // At least one section details should be available in the submit request... - // throw error if no section details. + errMsg = "Question Set From The Database returns Null"; + outgoingResponse.getResult().clear(); + break; + } + + hierarchySection.put(Constants.SCORE_CUTOFF_TYPE, scoreCutOffType); + List> questionsListFromSubmitRequest = new ArrayList<>(); + if (userSectionData.containsKey(Constants.CHILDREN) && !ObjectUtils.isEmpty(userSectionData.get(Constants.CHILDREN))) { + questionsListFromSubmitRequest = (List>) userSectionData.get(Constants.CHILDREN); + } + Map result = new HashMap<>(); + switch (scoreCutOffType) { + case Constants.ASSESSMENT_LEVEL_SCORE_CUTOFF: { + result.putAll(createResponseMapWithProperStructure(hierarchySection, assessUtilServ.validateQumlAssessment(questionsListFromAssessmentHierarchy, questionsListFromSubmitRequest))); + outgoingResponse.getResult().putAll(calculateAssessmentFinalResults(result)); + writeDataToDatabaseAndTriggerKafkaEvent(submitRequest, userId, questionSetFromAssessment, result, (String) assessmentHierarchy.get(Constants.PRIMARY_CATEGORY)); + return outgoingResponse; + } + case Constants.SECTION_LEVEL_SCORE_CUTOFF: { + result.putAll(createResponseMapWithProperStructure(hierarchySection, assessUtilServ.validateQumlAssessment(questionsListFromAssessmentHierarchy, questionsListFromSubmitRequest))); + sectionLevelsResults.add(result); + } + break; + default: + break; + } + + } else { + hierarchySection.put(Constants.SCORE_CUTOFF_TYPE, scoreCutOffType); + List> questionsListFromSubmitRequest = new ArrayList<>(); + if (userSectionData.containsKey(Constants.CHILDREN) && !ObjectUtils.isEmpty(userSectionData.get(Constants.CHILDREN))) { + questionsListFromSubmitRequest = (List>) userSectionData.get(Constants.CHILDREN); + } + List desiredKeys = Lists.newArrayList(Constants.IDENTIFIER); + List questionsList = questionsListFromSubmitRequest.stream().flatMap(x -> desiredKeys.stream().filter(x::containsKey).map(x::get)).collect(toList()); + questionsListFromAssessmentHierarchy = questionsList.stream().map(object -> Objects.toString(object, null)).collect(Collectors.toList()); + Map result = new HashMap<>(); + switch (scoreCutOffType) { + case Constants.ASSESSMENT_LEVEL_SCORE_CUTOFF: { + result.putAll(createResponseMapWithProperStructure(hierarchySection, assessUtilServ.validateQumlAssessment(questionsListFromAssessmentHierarchy, questionsListFromSubmitRequest))); + outgoingResponse.getResult().putAll(calculateAssessmentFinalResults(result)); + return outgoingResponse; + } + case Constants.SECTION_LEVEL_SCORE_CUTOFF: { + result.putAll(createResponseMapWithProperStructure(hierarchySection, assessUtilServ.validateQumlAssessment(questionsListFromAssessmentHierarchy, questionsListFromSubmitRequest))); + sectionLevelsResults.add(result); + } + break; + default: + break; } } } + if (errMsg.isEmpty() && !ObjectUtils.isEmpty(scoreCutOffType) && scoreCutOffType.equalsIgnoreCase(Constants.SECTION_LEVEL_SCORE_CUTOFF)) { + Map result = calculateSectionFinalResults(sectionLevelsResults); + outgoingResponse.getResult().putAll(result); + writeDataToDatabaseAndTriggerKafkaEvent(submitRequest, userId, questionSetFromAssessment, result, (String) assessmentHierarchy.get(Constants.PRIMARY_CATEGORY)); + return outgoingResponse; + } + } + if (StringUtils.isNotBlank(errMsg)) { + outgoingResponse.getParams().setStatus(Constants.FAILED); + outgoingResponse.getParams().setErrmsg(errMsg); + outgoingResponse.setResponseCode(HttpStatus.BAD_REQUEST); } return outgoingResponse; } - private Map getReadHierarchyApiResponse(String assessmentIdentifier, String token) { + private void writeDataToDatabaseAndTriggerKafkaEvent(Map submitRequest, String userId, Map questionSetFromAssessment, Map result, String primaryCategory) { try { - StringBuilder sbUrl = new StringBuilder(cbExtServerProperties.getAssessmentHost()); - sbUrl.append(cbExtServerProperties.getAssessmentHierarchyReadPath()); - String serviceURL = sbUrl.toString().replace(Constants.IDENTIFIER_REPLACER, assessmentIdentifier); - Map headers = new HashMap<>(); - headers.put(Constants.X_AUTH_TOKEN, token); - headers.put(Constants.AUTHORIZATION, cbExtServerProperties.getSbApiKey()); - Object o = outboundRequestHandlerService.fetchUsingGetWithHeaders(serviceURL, headers); - return mapper.convertValue(o, Map.class); + if (questionSetFromAssessment.get(Constants.START_TIME) != null) { + Long existingAssessmentStartTime = (Long) questionSetFromAssessment.get(Constants.START_TIME); + Timestamp startTime = new Timestamp(existingAssessmentStartTime); + Boolean isAssessmentUpdatedToDB = assessmentRepository.updateUserAssesmentDataToDB(userId, (String) submitRequest.get(Constants.IDENTIFIER), submitRequest, result, Constants.SUBMITTED, startTime); + if (Boolean.TRUE.equals(isAssessmentUpdatedToDB)) { + Map kafkaResult = new HashMap<>(); + kafkaResult.put(Constants.CONTENT_ID_KEY, submitRequest.get(Constants.IDENTIFIER)); + kafkaResult.put(Constants.COURSE_ID, submitRequest.get(Constants.COURSE_ID) != null ? submitRequest.get(Constants.COURSE_ID) : ""); + kafkaResult.put(Constants.BATCH_ID, submitRequest.get(Constants.BATCH_ID) != null ? submitRequest.get(Constants.BATCH_ID) : ""); + kafkaResult.put(Constants.USER_ID, submitRequest.get(Constants.USER_ID)); + kafkaResult.put(Constants.ASSESSMENT_ID_KEY, submitRequest.get(Constants.IDENTIFIER)); + kafkaResult.put(Constants.PRIMARY_CATEGORY, primaryCategory); + kafkaResult.put(Constants.TOTAL_SCORE, result.get(Constants.OVERALL_RESULT)); + if ((primaryCategory.equalsIgnoreCase("Competency Assessment") && submitRequest.containsKey("competencies_v3") && submitRequest.get("competencies_v3") != null)) { + Object[] obj = (Object[]) JSON.parse((String) submitRequest.get("competencies_v3")); + if (obj != null) { + Object map = obj[0]; + ObjectMapper m = new ObjectMapper(); + Map props = m.convertValue(map, Map.class); + kafkaResult.put(Constants.COMPETENCY, props.isEmpty() ? "" : props); + System.out.println(obj); + + } + System.out.println(obj); + } + logger.info(kafkaResult.toString()); + kafkaProducer.push(serverProperties.getAssessmentSubmitTopic(), kafkaResult); + } + } } catch (Exception e) { - logger.error(e); - throw new ApplicationLogicError(e.getMessage()); + logger.info(e.getMessage()); + } + } + + private String validateSubmitAssessmentRequest(Map submitRequest, String authUserToken, List> hierarchySectionList, List> sectionListFromSubmitRequest, Map assessmentHierarchy) throws IOException { + String userId = validateAuthTokenAndFetchUserId(authUserToken); + if (ObjectUtils.isEmpty(userId)) { + return Constants.USER_ID_DOESNT_EXIST; + } + submitRequest.put(Constants.USER_ID, userId); + if (StringUtils.isEmpty((String) submitRequest.get(Constants.IDENTIFIER))) { + return Constants.INVALID_ASSESSMENT_ID; + } + String assessmentIdFromRequest = (String) submitRequest.get(Constants.IDENTIFIER); + String errMsg = fetchReadHierarchyDetails(assessmentHierarchy, authUserToken, assessmentIdFromRequest); + if (!errMsg.isEmpty()) { + return errMsg; + } + if (ObjectUtils.isEmpty(assessmentHierarchy)) { + return Constants.READ_ASSESSMENT_FAILED; + } + hierarchySectionList.addAll((List>) assessmentHierarchy.get(Constants.CHILDREN)); + sectionListFromSubmitRequest.addAll((List>) submitRequest.get(Constants.CHILDREN)); + if (((String) (assessmentHierarchy.get(Constants.PRIMARY_CATEGORY))).equalsIgnoreCase(Constants.PRACTICE_QUESTION_SET)) + return ""; + List> existingDataList = assessmentRepository.fetchUserAssessmentDataFromDB(userId, assessmentIdFromRequest); + if (!existingDataList.isEmpty()) { + if (!((String) existingDataList.get(0).get(Constants.STATUS)).equalsIgnoreCase(Constants.SUBMITTED)) { + Date assessmentStartTime = (!existingDataList.isEmpty()) ? (Date) existingDataList.get(0).get(Constants.START_TIME) : null; + if (assessmentStartTime == null) { + return Constants.READ_ASSESSMENT_START_TIME_FAILED; + } + int expectedDuration = (Integer) assessmentHierarchy.get(Constants.EXPECTED_DURATION); + if (serverProperties.getUserAssessmentSubmissionDuration().isEmpty()) { + serverProperties.setUserAssessmentSubmissionDuration("120"); + } + Timestamp later = calculateAssessmentSubmitTime(expectedDuration, new Timestamp(assessmentStartTime.getTime()), Integer.parseInt(serverProperties.getUserAssessmentSubmissionDuration())); + Timestamp submissionTime = new Timestamp(new Date().getTime()); + int time = submissionTime.compareTo(later); + if (time <= 0) { + List desiredKeys = Lists.newArrayList(Constants.IDENTIFIER); + List hierarchySectionIds = hierarchySectionList.stream().flatMap(x -> desiredKeys.stream().filter(x::containsKey).map(x::get)).collect(toList()); + List submitSectionIds = sectionListFromSubmitRequest.stream().flatMap(x -> desiredKeys.stream().filter(x::containsKey).map(x::get)).collect(toList()); + if (!new HashSet<>(hierarchySectionIds).containsAll(submitSectionIds)) { + return Constants.WRONG_SECTION_DETAILS; + } else { + String areQuestionIdsSame = validateIfQuestionIdsAreSame(submitRequest, sectionListFromSubmitRequest, desiredKeys, userId); + if (!areQuestionIdsSame.isEmpty()) return areQuestionIdsSame; + } + } else { + return Constants.ASSESSMENT_SUBMIT_EXPIRED; + } + } else { + return Constants.ASSESSMENT_ALREADY_SUBMITTED; + } + } else { + return Constants.USER_ASSESSMENT_DATA_NOT_PRESENT; } + return ""; } - private SBApiResponse prepareAssessmentResponse(Map hierarchyResponse, boolean isSuccess) { - SBApiResponse outgoingResponse = new SBApiResponse(); - outgoingResponse.setId(Constants.API_QUESTIONSET_HIERARCHY_GET); - outgoingResponse.setVer(Constants.VER); - outgoingResponse.getParams().setResmsgid(UUID.randomUUID().toString()); - if (isSuccess) { - outgoingResponse.getParams().setStatus(Constants.SUCCESS); - outgoingResponse.setResponseCode(HttpStatus.OK); - readAssessmentLevelData(hierarchyResponse, outgoingResponse); + private String validateIfQuestionIdsAreSame(Map submitRequest, List> sectionListFromSubmitRequest, List desiredKeys, String userId) { + List> existingDataList = assessmentRepository.fetchUserAssessmentDataFromDB(userId, (String) submitRequest.get(Constants.IDENTIFIER)); + String questionSetFromAssessmentString = (!existingDataList.isEmpty()) ? (String) existingDataList.get(0).get(Constants.ASSESSMENT_READ_RESPONSE) : ""; + if (!questionSetFromAssessmentString.isEmpty()) { + Map questionSetFromAssessment = new Gson().fromJson(questionSetFromAssessmentString, new TypeToken>() { + }.getType()); + if (questionSetFromAssessment != null && questionSetFromAssessment.get(Constants.CHILDREN) != null) { + List> sections = (List>) questionSetFromAssessment.get(Constants.CHILDREN); + List desiredKey = Lists.newArrayList(Constants.CHILD_NODES); + List questionList = sections.stream().flatMap(x -> desiredKey.stream().filter(x::containsKey).map(x::get)).collect(toList()); + List questionIdsFromAssessmentHierarchy = new ArrayList<>(); + List> questionsListFromSubmitRequest = new ArrayList<>(); + for (Object question : questionList) { + questionIdsFromAssessmentHierarchy.addAll((List) question); + } + for (Map userSectionData : sectionListFromSubmitRequest) { + if (userSectionData.containsKey(Constants.CHILDREN) && !ObjectUtils.isEmpty(userSectionData.get(Constants.CHILDREN))) { + questionsListFromSubmitRequest.addAll((List>) userSectionData.get(Constants.CHILDREN)); + } + } + List userQuestionIdsFromSubmitRequest = questionsListFromSubmitRequest.stream().flatMap(x -> desiredKeys.stream().filter(x::containsKey).map(x::get)).collect(Collectors.toList()); + if (!new HashSet<>(questionIdsFromAssessmentHierarchy).containsAll(userQuestionIdsFromSubmitRequest)) { + return Constants.ASSESSMENT_SUBMIT_INVALID_QUESTION; + } + } } else { - outgoingResponse.getParams().setStatus(Constants.FAILED); - outgoingResponse.setResponseCode(HttpStatus.INTERNAL_SERVER_ERROR); + return Constants.ASSESSMENT_SUBMIT_QUESTION_READ_FAILED; } - return outgoingResponse; + return ""; + } + + private Timestamp calculateAssessmentSubmitTime(int expectedDuration, Timestamp assessmentStartTime, int bufferTime) { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(assessmentStartTime.getTime()); + if (bufferTime > 0) { + cal.add(Calendar.SECOND, expectedDuration + Integer.parseInt(serverProperties.getUserAssessmentSubmissionDuration())); + } else { + cal.add(Calendar.SECOND, expectedDuration); + } + return new Timestamp(cal.getTime().getTime()); + } + + private Map calculateAssessmentFinalResults(Map assessmentLevelResult) { + Map res = new HashMap<>(); + try { + res.put(Constants.CHILDREN, Collections.singletonList(assessmentLevelResult)); + Double result = (Double) assessmentLevelResult.get(Constants.RESULT); + res.put(Constants.OVERALL_RESULT, result); + res.put(Constants.TOTAL, assessmentLevelResult.get(Constants.TOTAL)); + res.put(Constants.BLANK, assessmentLevelResult.get(Constants.BLANK)); + res.put(Constants.CORRECT, assessmentLevelResult.get(Constants.CORRECT)); + res.put(Constants.PASS_PERCENTAGE, assessmentLevelResult.get(Constants.PASS_PERCENTAGE)); + res.put(Constants.INCORRECT, assessmentLevelResult.get(Constants.INCORRECT)); + Integer minimumPassPercentage = (Integer) assessmentLevelResult.get(Constants.PASS_PERCENTAGE); + res.put(Constants.PASS, result >= minimumPassPercentage); + } catch (Exception e) { + logger.info(e.getMessage()); + } + return res; } - private void readAssessmentLevelData(Map assessmentAllDetail, SBApiResponse outgoingResponse) { - List assessmentParams = cbExtServerProperties.getAssessmentLevelParams(); + private Map calculateSectionFinalResults(List> sectionLevelResults) { + Map res = new HashMap<>(); + Double result; + Integer correct = 0; + Integer blank = 0; + Integer inCorrect = 0; + Integer total = 0; + int pass = 0; + Double totalResult = 0.0; + try { + for (Map sectionChildren : sectionLevelResults) { + res.put(Constants.CHILDREN, sectionLevelResults); + result = (Double) sectionChildren.get(Constants.RESULT); + totalResult += result; + total += (Integer) sectionChildren.get(Constants.TOTAL); + blank += (Integer) sectionChildren.get(Constants.BLANK); + correct += (Integer) sectionChildren.get(Constants.CORRECT); + inCorrect += (Integer) sectionChildren.get(Constants.INCORRECT); + Integer minimumPassPercentage = (Integer) sectionChildren.get(Constants.PASS_PERCENTAGE); + if (result >= minimumPassPercentage) { + pass++; + } + } + res.put(Constants.OVERALL_RESULT, totalResult / sectionLevelResults.size()); + res.put(Constants.TOTAL, total); + res.put(Constants.BLANK, blank); + res.put(Constants.CORRECT, correct); + res.put(Constants.INCORRECT, inCorrect); + res.put(Constants.PASS, (pass == sectionLevelResults.size())); + } catch (Exception e) { + logger.info(e.getMessage()); + } + return res; + } + + private Map readAssessmentLevelData(Map assessmentAllDetail) { + List assessmentParams = serverProperties.getAssessmentLevelParams(); Map assessmentFilteredDetail = new HashMap<>(); for (String assessmentParam : assessmentParams) { if ((assessmentAllDetail.containsKey(assessmentParam))) { @@ -233,15 +611,14 @@ private void readAssessmentLevelData(Map assessmentAllDetail, SB } } readSectionLevelParams(assessmentAllDetail, assessmentFilteredDetail); - outgoingResponse.getResult().put(Constants.QUESTION_SET, assessmentFilteredDetail); + return assessmentFilteredDetail; } - private void readSectionLevelParams(Map assessmentAllDetail, - Map assessmentFilteredDetail) { + private void readSectionLevelParams(Map assessmentAllDetail, Map assessmentFilteredDetail) { List> sectionResponse = new ArrayList<>(); - List sectionParams = cbExtServerProperties.getAssessmentSectionParams(); + List sectionIdList = new ArrayList<>(); + List sectionParams = serverProperties.getAssessmentSectionParams(); List> sections = (List>) assessmentAllDetail.get(Constants.CHILDREN); - List sectionIdList = new ArrayList(); for (Map section : sections) { sectionIdList.add((String) section.get(Constants.IDENTIFIER)); Map newSection = new HashMap<>(); @@ -250,7 +627,7 @@ private void readSectionLevelParams(Map assessmentAllDetail, newSection.put(sectionParam, section.get(sectionParam)); } } - List allQuestionIdList = new ArrayList(); + List allQuestionIdList = new ArrayList<>(); List> questions = (List>) section.get(Constants.CHILDREN); for (Map question : questions) { allQuestionIdList.add((String) question.get(Constants.IDENTIFIER)); @@ -259,7 +636,7 @@ private void readSectionLevelParams(Map assessmentAllDetail, List childNodeList = new ArrayList<>(); if (!ObjectUtils.isEmpty(section.get(Constants.MAX_QUESTIONS))) { int maxQuestions = (int) section.get(Constants.MAX_QUESTIONS); - childNodeList = allQuestionIdList.stream().limit(maxQuestions).collect(Collectors.toList()); + childNodeList = allQuestionIdList.stream().limit(maxQuestions).collect(toList()); } newSection.put(Constants.CHILD_NODES, childNodeList); sectionResponse.add(newSection); @@ -268,150 +645,104 @@ private void readSectionLevelParams(Map assessmentAllDetail, assessmentFilteredDetail.put(Constants.CHILD_NODES, sectionIdList); } - private List getQuestionIdList(Map questionListRequest) throws Exception { - if (questionListRequest.containsKey(Constants.REQUEST)) { - Map request = (Map) questionListRequest.get(Constants.REQUEST); - if ((!ObjectUtils.isEmpty(request)) && request.containsKey(Constants.SEARCH)) { - Map searchObj = (Map) request.get(Constants.SEARCH); - if (!ObjectUtils.isEmpty(searchObj) && searchObj.containsKey(Constants.IDENTIFIER)) { - List identifierList = (List) searchObj.get(Constants.IDENTIFIER); - if (!CollectionUtils.isEmpty(identifierList)) { - return identifierList; + private List getQuestionIdList(Map questionListRequest) { + try { + if (questionListRequest.containsKey(Constants.REQUEST)) { + Map request = (Map) questionListRequest.get(Constants.REQUEST); + if ((!ObjectUtils.isEmpty(request)) && request.containsKey(Constants.SEARCH)) { + Map searchObj = (Map) request.get(Constants.SEARCH); + if (!ObjectUtils.isEmpty(searchObj) && searchObj.containsKey(Constants.IDENTIFIER) && !CollectionUtils.isEmpty((List) searchObj.get(Constants.IDENTIFIER))) { + return (List) searchObj.get(Constants.IDENTIFIER); } } } - } - throw new Exception("Failed to process the questionList request body."); - } - - private Map readQuestionDetails(List identifiers) throws Exception { - try { - StringBuilder sbUrl = new StringBuilder(cbExtServerProperties.getAssessmentHost()); - sbUrl.append(cbExtServerProperties.getAssessmentQuestionListPath()); - Map headers = new HashMap<>(); - headers.put(Constants.AUTHORIZATION, cbExtServerProperties.getSbApiKey()); - Map requestBody = new HashMap(); - Map requestData = new HashMap(); - Map searchData = new HashMap(); - searchData.put(Constants.IDENTIFIER, identifiers); - requestData.put(Constants.SEARCH, searchData); - requestBody.put(Constants.REQUEST, requestData); - return outboundRequestHandlerService.fetchResultUsingPost(sbUrl.toString(), requestBody, headers); } catch (Exception e) { - logger.error(e); - throw new Exception("Failed to process the readQuestionDetails."); + logger.error(String.format("Failed to process the questionList request body. %s", e.getMessage())); } + return Collections.emptyList(); } - private Map filterQuestionMapDetail(Map questionMapResponse) { - List questionParams = cbExtServerProperties.getAssessmentQuestionParams(); - Map updatedQuestionMap = new HashMap(); - for (String questionParam : questionParams) { - if (questionMapResponse.containsKey(questionParam)) { - updatedQuestionMap.put(questionParam, questionMapResponse.get(questionParam)); - } - } - if (questionMapResponse.containsKey(Constants.CHOICES) && updatedQuestionMap.containsKey(Constants.PRIMARY_CATEGORY) && !updatedQuestionMap.get(Constants.PRIMARY_CATEGORY).toString().equalsIgnoreCase(Constants.FTB_QUESTION)) { - Map choicesObj = (Map) questionMapResponse.get(Constants.CHOICES); - Map updatedChoicesMap = new HashMap<>(); - if (choicesObj.containsKey(Constants.OPTIONS)) { - List> optionsMapList = (List>) choicesObj - .get(Constants.OPTIONS); - updatedChoicesMap.put(Constants.OPTIONS, optionsMapList); - } - updatedQuestionMap.put(Constants.CHOICES, updatedChoicesMap); - } - if (questionMapResponse.containsKey(Constants.RHS_CHOICES) && updatedQuestionMap.containsKey(Constants.PRIMARY_CATEGORY) && updatedQuestionMap.get(Constants.PRIMARY_CATEGORY).toString().equalsIgnoreCase(Constants.MTF_QUESTION)) { - List rhsChoicesObj = (List) questionMapResponse.get(Constants.RHS_CHOICES); - updatedQuestionMap.put(Constants.RHS_CHOICES, rhsChoicesObj); + public Map createResponseMapWithProperStructure(Map hierarchySection, Map resultMap) { + Map sectionLevelResult = new HashMap<>(); + sectionLevelResult.put(Constants.IDENTIFIER, hierarchySection.get(Constants.IDENTIFIER)); + sectionLevelResult.put(Constants.OBJECT_TYPE, hierarchySection.get(Constants.OBJECT_TYPE)); + sectionLevelResult.put(Constants.PRIMARY_CATEGORY, hierarchySection.get(Constants.PRIMARY_CATEGORY)); + sectionLevelResult.put(Constants.PASS_PERCENTAGE, hierarchySection.get(Constants.MINIMUM_PASS_PERCENTAGE)); + Double result; + if (!ObjectUtils.isEmpty(resultMap)) { + result = (Double) resultMap.get(Constants.RESULT); + sectionLevelResult.put(Constants.RESULT, result); + sectionLevelResult.put(Constants.TOTAL, resultMap.get(Constants.TOTAL)); + sectionLevelResult.put(Constants.BLANK, resultMap.get(Constants.BLANK)); + sectionLevelResult.put(Constants.CORRECT, resultMap.get(Constants.CORRECT)); + sectionLevelResult.put(Constants.INCORRECT, resultMap.get(Constants.INCORRECT)); + } else { + result = 0.0; + sectionLevelResult.put(Constants.RESULT, result); + List childNodes = (List) hierarchySection.get(Constants.CHILDREN); + sectionLevelResult.put(Constants.TOTAL, childNodes.size()); + sectionLevelResult.put(Constants.BLANK, childNodes.size()); + sectionLevelResult.put(Constants.CORRECT, 0); + sectionLevelResult.put(Constants.INCORRECT, 0); } - - return updatedQuestionMap; + sectionLevelResult.put(Constants.PASS, result >= ((Integer) hierarchySection.get(Constants.MINIMUM_PASS_PERCENTAGE))); + sectionLevelResult.put(Constants.OVERALL_RESULT, result); + return sectionLevelResult; } - private SBApiResponse prepareQuestionResponse(List updatedQuestions, boolean isSuccess) { - SBApiResponse outgoingResponse = new SBApiResponse(); - outgoingResponse.setId(Constants.API_QUESTIONS_LIST); - outgoingResponse.setVer(Constants.VER); - if (isSuccess) { - outgoingResponse.setResponseCode(HttpStatus.OK); - outgoingResponse.getResult().put(Constants.QUESTIONS, updatedQuestions); - } else { - outgoingResponse.setResponseCode(HttpStatus.INTERNAL_SERVER_ERROR); - } - return outgoingResponse; + private SBApiResponse createDefaultResponse(String api) { + SBApiResponse response = new SBApiResponse(); + response.setId(api); + response.setVer(Constants.VER); + response.getParams().setResmsgid(UUID.randomUUID().toString()); + response.getParams().setStatus(Constants.SUCCESS); + response.setResponseCode(HttpStatus.OK); + response.setTs(DateTime.now().toString()); + return response; } - private void validateSectionLevelScore(SBApiResponse outgoingResponse, Map userSectionData, - SunbirdApiResp assessmentHierarchy) { + private Boolean validateQuestionListRequest(List identifierList, List questionsFromAssessment) { + return (new HashSet<>(questionsFromAssessment).containsAll(identifierList)) ? Boolean.TRUE : Boolean.FALSE; } - private void validateAssessmentLevelScore(SBApiResponse outgoingResponse, Map userSectionData, - Map assessmentHierarchy) { - // First Get the Hierarchy of given AssessmentId - List> hierarchySectionList = (List>) assessmentHierarchy - .get(Constants.CHILDREN); - if (CollectionUtils.isEmpty(hierarchySectionList)) { - logger.error(new Exception("There are no section details in Assessment hierarchy.")); - // TODO Throw error - return; - } - String userSectionId = (String) userSectionData.get(Constants.IDENTIFIER); - - Map hierarchySection = null; - for (Map section : hierarchySectionList) { - String hierarchySectionId = (String) section.get(Constants.IDENTIFIER); - if (userSectionId.equalsIgnoreCase(hierarchySectionId)) { - hierarchySection = section; - break; + public SBApiResponse retakeAssessment(String assessmentIdentifier, String token) throws Exception { + logger.info("AssessmentServiceV2Impl::retakeAssessment... Started"); + SBApiResponse response = createDefaultResponse(Constants.API_RETAKE_ASSESSMENT_GET); + String errMsg = ""; + int retakeAttemptsAllowed = 0; + int retakeAttemptsConsumed = 0; + try { + String userId = validateAuthTokenAndFetchUserId(token); + if (userId != null) { + List> existingDataList = assessmentRepository.fetchUserAssessmentDataFromDB(userId, assessmentIdentifier); + Map assessmentAllDetail = new HashMap<>(); + errMsg = fetchReadHierarchyDetails(assessmentAllDetail, token, assessmentIdentifier); + if (assessmentAllDetail.get(Constants.MAX_ASSESSMENT_RETAKE_ATTEMPTS) != null) { + retakeAttemptsAllowed = (int) assessmentAllDetail.get(Constants.MAX_ASSESSMENT_RETAKE_ATTEMPTS); + } + retakeAttemptsConsumed = calculateAssessmentRetakeCount(existingDataList); + } else { + errMsg = Constants.USER_ID_DOESNT_EXIST; } + } catch (Exception e) { + logger.error(String.format("Exception in %s : %s", "read Assessment", e.getMessage()), e); + errMsg = "Failed to read Assessment. Exception: " + e.getMessage(); } - - if (ObjectUtils.isEmpty(hierarchySection)) { - // TODO - throw error - return; - } - - // We have both hierarchySection and userSection - // Get the list of question Identifier's from userSectionData - List questionIdList = new ArrayList(); - List> userQuestionList = (List>) hierarchySection.get(Constants.CHILDREN); - for (Map question : userQuestionList) { - questionIdList.add((String) question.get(Constants.IDENTIFIER)); + if (StringUtils.isNotBlank(errMsg)) { + response.getParams().setStatus(Constants.FAILED); + response.getParams().setErrmsg(errMsg); + response.setResponseCode(HttpStatus.INTERNAL_SERVER_ERROR); + } else { + response.getResult().put(Constants.TOTAL_RETAKE_ATTEMPTS_ALLOWED, retakeAttemptsAllowed); + response.getResult().put(Constants.RETAKE_ATTEMPTS_CONSUMED, retakeAttemptsConsumed); } - - // We have both answer and user given data. This needs to be compared and result - // should be return. - Map resultMap = assessUtilServ.validateQumlAssessment(questionIdList, - (List>) userSectionData.get(Constants.CHILDREN)); - - Double result = (Double) resultMap.get(Constants.RESULT); - int passPercentage = (Integer) hierarchySection.get(Constants.MINIMUM_PASS_PERCENTAGE); - Map sectionLevelResult = new HashMap(); - sectionLevelResult.put(Constants.IDENTIFIER, hierarchySection.get(Constants.IDENTIFIER)); - sectionLevelResult.put(Constants.OBJECT_TYPE, hierarchySection.get(Constants.OBJECT_TYPE)); - sectionLevelResult.put(Constants.PRIMARY_CATEGORY, hierarchySection.get(Constants.PRIMARY_CATEGORY)); - sectionLevelResult.put(Constants.SCORE_CUTOFF_TYPE, hierarchySection.get(Constants.SCORE_CUTOFF_TYPE)); - sectionLevelResult.put(Constants.PASS_PERCENTAGE, passPercentage); - sectionLevelResult.put(Constants.RESULT, result); - sectionLevelResult.put(Constants.TOTAL, resultMap.get(Constants.TOTAL)); - sectionLevelResult.put(Constants.BLANK, resultMap.get(Constants.BLANK)); - sectionLevelResult.put(Constants.CORRECT, resultMap.get(Constants.CORRECT)); - sectionLevelResult.put(Constants.PASS_PERCENTAGE, hierarchySection.get(Constants.MINIMUM_PASS_PERCENTAGE)); - sectionLevelResult.put(Constants.INCORRECT, resultMap.get(Constants.INCORRECT)); - sectionLevelResult.put(Constants.PASS, result >= passPercentage); - - List> sectionChildren = new ArrayList>(); - sectionChildren.add(sectionLevelResult); - outgoingResponse.getResult().put(Constants.CHILDREN, sectionChildren); - - outgoingResponse.getResult().put(Constants.OVERALL_RESULT, result); - outgoingResponse.getResult().put(Constants.TOTAL, resultMap.get(Constants.TOTAL)); - outgoingResponse.getResult().put(Constants.BLANK, resultMap.get(Constants.BLANK)); - outgoingResponse.getResult().put(Constants.CORRECT, resultMap.get(Constants.CORRECT)); - outgoingResponse.getResult().put(Constants.PASS_PERCENTAGE, hierarchySection.get(Constants.MINIMUM_PASS_PERCENTAGE)); - outgoingResponse.getResult().put(Constants.INCORRECT, resultMap.get(Constants.INCORRECT)); - outgoingResponse.getResult().put(Constants.PASS, result >= passPercentage); + return response; } + private int calculateAssessmentRetakeCount(List> userAssessmentData) { + List desiredKeys = Lists.newArrayList(Constants.SUBMIT_ASSESSMENT_RESPONSE); + List values = userAssessmentData.stream().flatMap(x -> desiredKeys.stream().filter(x::containsKey).map(x::get)).collect(toList()); + Iterables.removeIf(values, Predicates.isNull()); + return values.size(); + } } \ No newline at end of file diff --git a/src/main/java/org/sunbird/assessment/service/AssessmentUtilServiceV2.java b/src/main/java/org/sunbird/assessment/service/AssessmentUtilServiceV2.java index dc3f02514..4cbdc29cf 100644 --- a/src/main/java/org/sunbird/assessment/service/AssessmentUtilServiceV2.java +++ b/src/main/java/org/sunbird/assessment/service/AssessmentUtilServiceV2.java @@ -6,4 +6,12 @@ public interface AssessmentUtilServiceV2 { public Map validateQumlAssessment(List originalQuestionList, List> userQuestionList); + + public String fetchQuestionIdentifierValue(List identifierList, List questionList, String primaryCategory) throws Exception; + + Map filterQuestionMapDetail(Map questionMapResponse, String primaryCategory); + + List> readQuestionDetails(List identifiers); + + public Map getReadHierarchyApiResponse(String assessmentIdentifier, String token); } diff --git a/src/main/java/org/sunbird/assessment/service/AssessmentUtilServiceV2Impl.java b/src/main/java/org/sunbird/assessment/service/AssessmentUtilServiceV2Impl.java index 520c710b2..e5ba2e420 100644 --- a/src/main/java/org/sunbird/assessment/service/AssessmentUtilServiceV2Impl.java +++ b/src/main/java/org/sunbird/assessment/service/AssessmentUtilServiceV2Impl.java @@ -6,47 +6,39 @@ import java.util.List; import java.util.Map; +import com.fasterxml.jackson.core.type.TypeReference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.sunbird.cache.RedisCacheMgr; +import org.sunbird.common.service.OutboundRequestHandlerServiceImpl; +import org.sunbird.common.util.CbExtServerProperties; import org.sunbird.common.util.Constants; -import org.sunbird.core.exception.ApplicationLogicError; -import org.sunbird.core.logger.CbExtLogger; -import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; @Service public class AssessmentUtilServiceV2Impl implements AssessmentUtilServiceV2 { + @Autowired + CbExtServerProperties serverProperties; + + @Autowired + OutboundRequestHandlerServiceImpl outboundRequestHandlerService; + @Autowired RedisCacheMgr redisCacheMgr; - - ObjectMapper mapper = new ObjectMapper(); - - private CbExtLogger logger = new CbExtLogger(getClass().getName()); - - public static final String QUESTION_TYPE = "qType"; - public static final String OPTIONS = "options"; - public static final String IS_CORRECT = "isCorrect"; - public static final String OPTION_ID = "optionId"; - public static final String MCQ_SCA = "mcq-sca"; - public static final String MCQ_MCA = "mcq-mca"; - public static final String FTB = "ftb"; - public static final String MTF = "mtf"; - public static final String QUESTION_ID = "questionId"; - public static final String RESPONSE = "response"; - public static final String ANSWER = "answer"; - public static final String VALUE = "value"; - public static final String EDITOR_STATE = "editorState"; - public static final String BODY = "body"; - public static final String SELECTED_ANSWER = "selectedAnswer"; - public static final String INDEX = "index"; + + @Autowired + ObjectMapper mapper; + + private Logger logger = LoggerFactory.getLogger(AssessmentUtilServiceV2Impl.class); public Map validateQumlAssessment(List originalQuestionList, - List> userQuestionList) { + List> userQuestionList) { try { Integer correct = 0; Integer blank = 0; @@ -57,38 +49,35 @@ public Map validateQumlAssessment(List originalQuestionL Map answers = getQumlAnswers(originalQuestionList); for (Map question : userQuestionList) { List marked = new ArrayList<>(); - if (question.containsKey(QUESTION_TYPE)) { - String questionType = ((String) question.get(QUESTION_TYPE)).toLowerCase(); - Map editorStateObj = (Map) question.get(EDITOR_STATE); - List> options = (List>) editorStateObj.get(OPTIONS); + if (question.containsKey(Constants.QUESTION_TYPE)) { + String questionType = ((String) question.get(Constants.QUESTION_TYPE)).toLowerCase(); + Map editorStateObj = (Map) question.get(Constants.EDITOR_STATE); + List> options = (List>) editorStateObj + .get(Constants.OPTIONS); switch (questionType) { - case MTF: - for (Map option : options) { - marked.add(option.get(INDEX).toString() + "-" - + option.get(SELECTED_ANSWER).toString().toLowerCase()); - } - break; - case FTB: - for (Map option : options) { - marked.add((String) option.get(SELECTED_ANSWER)); - } - break; - case MCQ_SCA: - case MCQ_MCA: - for (Map option : options) { - if ((boolean) option.get(SELECTED_ANSWER)) { - marked.add((String) option.get(INDEX)); + case Constants.MTF: + for (Map option : options) { + marked.add(option.get(Constants.INDEX).toString() + "-" + + option.get(Constants.SELECTED_ANSWER).toString().toLowerCase()); } - } - break; - default: - break; + break; + case Constants.FTB: + for (Map option : options) { + marked.add((String) option.get(Constants.SELECTED_ANSWER)); + } + break; + case Constants.MCQ_SCA: + case Constants.MCQ_MCA: + for (Map option : options) { + if ((boolean) option.get(Constants.SELECTED_ANSWER)) { + marked.add((String) option.get(Constants.INDEX)); + } + } + break; + default: + break; } - } else { - // TODO - how to handle this case?? - // Currently throw error } - if (CollectionUtils.isEmpty(marked)) blank++; else { @@ -102,70 +91,81 @@ public Map validateQumlAssessment(List originalQuestionL else inCorrect++; } - total++; } // Increment the blank counter for skipped question objects if (answers.size() > userQuestionList.size()) { blank += answers.size() - userQuestionList.size(); } - result = ((correct * 100d) / (correct + blank + inCorrect)); - resultMap.put("result", result); - resultMap.put("incorrect", inCorrect); - resultMap.put("blank", blank); - resultMap.put("correct", correct); - resultMap.put("total", total); + total = correct + blank + inCorrect; + resultMap.put(Constants.RESULT, ((correct * 100d) / total)); + resultMap.put(Constants.INCORRECT, inCorrect); + resultMap.put(Constants.BLANK, blank); + resultMap.put(Constants.CORRECT, correct); + resultMap.put(Constants.TOTAL, total); return resultMap; } catch (Exception ex) { - logger.error(ex); - throw new ApplicationLogicError("Error when verifying assessment. Error : " + ex.getMessage(), ex); + logger.error("Error when verifying assessment. Error : "); } + return new HashMap<>(); } private Map getQumlAnswers(List questions) throws Exception { Map ret = new HashMap<>(); + + Map> questionMap = new HashMap>(); + for (String questionId : questions) { List correctOption = new ArrayList<>(); - String strQuestion = redisCacheMgr.getCache(Constants.QUESTION_ID + questionId); - Map question = mapper.readValue(strQuestion, new TypeReference>() { - }); - if (ObjectUtils.isEmpty(question)) { - logger.error(new Exception("Failed to get the answer for question: " + questionId)); - // TODO - Need to handle this scenario. + Map question = new HashMap<>(); + String questionString = redisCacheMgr + .getCache(Constants.QUESTION_ID + questionId); + if (!ObjectUtils.isEmpty(question)) { + question = mapper.readValue(questionString, new TypeReference>() { + }); + } + else + { + logger.error("Failed to get the answer for question from redis cache: " + questionId); + questionMap = fetchQuestionMapDetails(questionId); + question = questionMap.get(questionId); } - if (question.containsKey(QUESTION_TYPE)) { - String questionType = ((String) question.get(QUESTION_TYPE)).toLowerCase(); - Map editorStateObj = (Map) question.get(EDITOR_STATE); - List> options = (List>) editorStateObj.get(OPTIONS); + if (question.containsKey(Constants.QUESTION_TYPE)) { + String questionType = ((String) question.get(Constants.QUESTION_TYPE)).toLowerCase(); + Map editorStateObj = (Map) question.get(Constants.EDITOR_STATE); + List> options = (List>) editorStateObj.get(Constants.OPTIONS); switch (questionType) { - case MTF: - for (Map option : options) { - Map valueObj = (Map) option.get(VALUE); - correctOption.add( - valueObj.get(VALUE).toString() + "-" + option.get(ANSWER).toString().toLowerCase()); - } - break; - case FTB: - for (Map option : options) { - correctOption.add((String) option.get(SELECTED_ANSWER)); - } - break; - case MCQ_SCA: - case MCQ_MCA: - for (Map option : options) { - if ((boolean) option.get(ANSWER)) { - Map valueObj = (Map) option.get(VALUE); - correctOption.add(valueObj.get(VALUE).toString()); + case Constants.MTF: + for (Map option : options) { + Map valueObj = (Map) option.get(Constants.VALUE); + correctOption.add(valueObj.get(Constants.VALUE).toString() + "-" + + option.get(Constants.ANSWER).toString().toLowerCase()); } - } - break; - default: - break; + break; + case Constants.FTB: + for (Map option : options) { + if ((boolean) option.get(Constants.ANSWER)) { + Map valueObj = (Map) option.get(Constants.VALUE); + correctOption.add(valueObj.get(Constants.BODY).toString()); + } + } + break; + case Constants.MCQ_SCA: + case Constants.MCQ_MCA: + for (Map option : options) { + if ((boolean) option.get(Constants.ANSWER)) { + Map valueObj = (Map) option.get(Constants.VALUE); + correctOption.add(valueObj.get(Constants.VALUE).toString()); + } + } + break; + default: + break; } } else { - for (Map options : (List>) question.get(OPTIONS)) { - if ((boolean) options.get(IS_CORRECT)) - correctOption.add(options.get(OPTION_ID).toString()); + for (Map options : (List>) question.get(Constants.OPTIONS)) { + if ((boolean) options.get(Constants.IS_CORRECT)) + correctOption.add(options.get(Constants.OPTION_ID).toString()); } } ret.put(question.get(Constants.IDENTIFIER).toString(), correctOption); @@ -173,4 +173,152 @@ private Map getQumlAnswers(List questions) throws Except return ret; } + + private Map> fetchQuestionMapDetails(String questionId) { + // Taking the list which was formed with the not found values in Redis, we are + // making an internal POST call to Question List API to fetch the details + Map> questionsMap = new HashMap<>(); + List> questionMapList = readQuestionDetails(Collections.singletonList(questionId)); + for (Map questionMapResponse : questionMapList) { + if (!ObjectUtils.isEmpty(questionMapResponse) + && Constants.OK.equalsIgnoreCase((String) questionMapResponse.get(Constants.RESPONSE_CODE))) { + List> questionMap = ((List>) ((Map) questionMapResponse + .get(Constants.RESULT)).get(Constants.QUESTIONS)); + for (Map question : questionMap) { + if (!ObjectUtils.isEmpty(questionMap)) { + questionsMap.put((String) question.get(Constants.IDENTIFIER), question); + redisCacheMgr.putCache(Constants.QUESTION_ID, question); + } + } + } + } + return questionsMap; + } + + @Override + public String fetchQuestionIdentifierValue(List identifierList, List questionList, String primaryCategory) + throws Exception { + List newIdentifierList = new ArrayList<>(); + newIdentifierList.addAll(identifierList); + + // Taking the list which was formed with the not found values in Redis, we are + // making an internal POST call to Question List API to fetch the details + if (!newIdentifierList.isEmpty()) { + List> questionMapList = readQuestionDetails(newIdentifierList); + for (Map questionMapResponse : questionMapList) { + if (!ObjectUtils.isEmpty(questionMapResponse) + && Constants.OK.equalsIgnoreCase((String) questionMapResponse.get(Constants.RESPONSE_CODE))) { + List> questionMap = ((List>) ((Map) questionMapResponse + .get(Constants.RESULT)).get(Constants.QUESTIONS)); + for (Map question : questionMap) { + if (!ObjectUtils.isEmpty(questionMap)) { + questionList.add(filterQuestionMapDetail(question, primaryCategory)); + } else { + logger.error(String.format("Failed to get Question Details for Id: %s", + question.get(Constants.IDENTIFIER).toString())); + return "Failed to get Question Details for Id: %s"; + } + } + } else { + logger.error( + String.format("Failed to get Question Details from the Question List API for the IDs: %s", + newIdentifierList.toString())); + return "Failed to get Question Details from the Question List API for the IDs"; + } + } + } + return ""; + } + + @Override + public Map filterQuestionMapDetail(Map questionMapResponse, String primaryCategory) { + List questionParams = serverProperties.getAssessmentQuestionParams(); + Map updatedQuestionMap = new HashMap<>(); + for (String questionParam : questionParams) { + if (questionMapResponse.containsKey(questionParam)) { + updatedQuestionMap.put(questionParam, questionMapResponse.get(questionParam)); + } + } + if (questionMapResponse.containsKey(Constants.EDITOR_STATE) + && primaryCategory.equalsIgnoreCase(Constants.PRACTICE_QUESTION_SET)) { + Map editorState = (Map) questionMapResponse.get(Constants.EDITOR_STATE); + updatedQuestionMap.put(Constants.EDITOR_STATE, editorState); + } + if (questionMapResponse.containsKey(Constants.CHOICES) + && updatedQuestionMap.containsKey(Constants.PRIMARY_CATEGORY) && !updatedQuestionMap + .get(Constants.PRIMARY_CATEGORY).toString().equalsIgnoreCase(Constants.FTB_QUESTION)) { + Map choicesObj = (Map) questionMapResponse.get(Constants.CHOICES); + Map updatedChoicesMap = new HashMap<>(); + if (choicesObj.containsKey(Constants.OPTIONS)) { + List> optionsMapList = (List>) choicesObj + .get(Constants.OPTIONS); + updatedChoicesMap.put(Constants.OPTIONS, optionsMapList); + } + updatedQuestionMap.put(Constants.CHOICES, updatedChoicesMap); + } + if (questionMapResponse.containsKey(Constants.RHS_CHOICES) + && updatedQuestionMap.containsKey(Constants.PRIMARY_CATEGORY) && updatedQuestionMap + .get(Constants.PRIMARY_CATEGORY).toString().equalsIgnoreCase(Constants.MTF_QUESTION)) { + List rhsChoicesObj = (List) questionMapResponse.get(Constants.RHS_CHOICES); + Collections.shuffle(rhsChoicesObj); + updatedQuestionMap.put(Constants.RHS_CHOICES, rhsChoicesObj); + } + + return updatedQuestionMap; + } + + @Override + public List> readQuestionDetails(List identifiers) { + try { + StringBuilder sbUrl = new StringBuilder(serverProperties.getAssessmentHost()); + sbUrl.append(serverProperties.getAssessmentQuestionListPath()); + Map headers = new HashMap<>(); + headers.put(Constants.AUTHORIZATION, serverProperties.getSbApiKey()); + Map requestBody = new HashMap<>(); + Map requestData = new HashMap<>(); + Map searchData = new HashMap<>(); + requestData.put(Constants.SEARCH, searchData); + requestBody.put(Constants.REQUEST, requestData); + List> questionDataList = new ArrayList<>(); + int chunkSize = 15; + for (int i = 0; i < identifiers.size(); i += chunkSize) { + List identifierList; + if ((i + chunkSize) >= identifiers.size()) { + identifierList = identifiers.subList(i, identifiers.size()); + } else { + identifierList = identifiers.subList(i, i + chunkSize); + } + searchData.put(Constants.IDENTIFIER, identifierList); + Map data = outboundRequestHandlerService.fetchResultUsingPost(sbUrl.toString(), + requestBody, headers); + if (!ObjectUtils.isEmpty(data)) { + questionDataList.add(data); + } + } + return questionDataList; + } catch (Exception e) { + logger.info(String.format("Failed to process the readQuestionDetails. %s", e.getMessage())); + } + return new ArrayList<>(); + } + + @Override + public Map getReadHierarchyApiResponse(String assessmentIdentifier, String token) { + try { + StringBuilder sbUrl = new StringBuilder(serverProperties.getAssessmentHost()); + sbUrl.append(serverProperties.getAssessmentHierarchyReadPath()); + String serviceURL = sbUrl.toString().replace(Constants.IDENTIFIER_REPLACER, assessmentIdentifier); + Map headers = new HashMap<>(); + headers.put(Constants.X_AUTH_TOKEN, token); + headers.put(Constants.AUTHORIZATION, serverProperties.getSbApiKey()); + logger.info(serviceURL); + Object o = outboundRequestHandlerService.fetchUsingGetWithHeaders(serviceURL, headers); + Map data = new ObjectMapper().convertValue(o, Map.class); + logger.info(data.toString()); + return data; + } catch (Exception e) { + logger.error("error in getReadHierarchyApiResponse " + e.getMessage()); + } + return new HashMap<>(); + } } \ No newline at end of file diff --git a/src/main/java/org/sunbird/common/util/CbExtServerProperties.java b/src/main/java/org/sunbird/common/util/CbExtServerProperties.java index 8c56b252b..c1c669e45 100644 --- a/src/main/java/org/sunbird/common/util/CbExtServerProperties.java +++ b/src/main/java/org/sunbird/common/util/CbExtServerProperties.java @@ -434,6 +434,18 @@ public class CbExtServerProperties { @Value("${es.user.report.include.fields}") private String esUserReportIncludeFields; + @Value("${kafka.topics.user.assessment.submit}") + private String assessmentSubmitTopic; + + + public String getAssessmentSubmitTopic() { + return assessmentSubmitTopic; + } + + public void setAssessmentSubmitTopic(String assessmentSubmitTopic) { + this.assessmentSubmitTopic = assessmentSubmitTopic; + } + public String getUserAssessmentSubmissionDuration() { return userAssessmentSubmissionDuration; } diff --git a/src/main/java/org/sunbird/common/util/Constants.java b/src/main/java/org/sunbird/common/util/Constants.java index 4a198c026..868ce2be6 100644 --- a/src/main/java/org/sunbird/common/util/Constants.java +++ b/src/main/java/org/sunbird/common/util/Constants.java @@ -558,6 +558,58 @@ public class Constants { public static final String MDO = "mdo"; public static final String BOARD = "board"; public static final String TRAINING_INSTITUTE = "TrainingInstitute"; + public static final String TOTAL_SCORE = "totalScore"; + public static final String SUBMIT_ASSESSMENT_RESPONSE = "submitassessmentresponse"; + public static final String PRACTICE_QUESTION_SET = "Practice Question Set"; + public static final String EXPECTED_DURATION = "expectedDuration"; + public static final String SUBMITTED = "SUBMITTED"; + public static final String NOT_SUBMITTED = "NOT SUBMITTED"; + public static final String END_TIME = "endtime"; + public static final String ASSESSMENT_ID_KEY = "assessmentId"; + public static final String START_TIME = "starttime"; + public static final String CONTENT_ID_KEY = "contentId"; + public static final String QUESTION_TYPE = "qType"; + public static final String SELECTED_ANSWER = "selectedAnswer"; + public static final String INDEX = "index"; + public static final String MCQ_SCA = "mcq-sca"; + public static final String MCQ_MCA = "mcq-mca"; + public static final String FTB = "ftb"; + public static final String MTF = "mtf"; + public static final String IS_CORRECT = "isCorrect"; + public static final String OPTION_ID = "optionId"; + + public static final String TABLE_USER_ASSESSMENT_DATA = "user_assessment_data"; + + + public static final String USER_ID_DOESNT_EXIST = "User Id doesn't exist! Please supply a valid auth token"; + public static final String ASSESSMENT_DATA_START_TIME_NOT_UPDATED = "Assessment Data & Start Time not updated in the DB! Please check!"; + public static final String FAILED_TO_GET_QUESTION_DETAILS = "Failed to get Question List data from the Question List Api! Please check!"; + + public static final String ASSESSMENT_HIERARCHY_READ_FAILED = "Assessment hierarchy read failed, failed to process request"; + public static final String ASSESSMENT_ID_KEY_IS_NOT_PRESENT_IS_EMPTY = "Assessment Id Key is not present/is empty"; + + public static final String USER_ASSESSMENT_DATA_NOT_PRESENT = "User Assessment Data not present in Databases"; + public static final String ASSESSMENT_ID_INVALID = "The Assessment Id is Invalid/Doesn't match with our records"; + public static final String IDENTIFIER_LIST_IS_EMPTY = "Identifier List is Empty"; + public static final String THE_QUESTIONS_IDS_PROVIDED_DONT_MATCH = "The Questions Ids Provided don't match the active user assessment session"; + public static final String ASSESSMENT_ID_INVALID_SESSION_EXPIRED = "Assessment Id Invalid/Session Expired/Redis Cache doesn't have this question list details"; + public static final String INVALID_ASSESSMENT_ID = "Invalid Assessment Id"; + public static final String READ_ASSESSMENT_FAILED = "Failed to read assessment hierarchy for the given AssessmentId."; + public static final String READ_ASSESSMENT_START_TIME_FAILED = "Failed to read the assessment start time."; + public static final String WRONG_SECTION_DETAILS = "Wrong section details."; + public static final String ASSESSMENT_SUBMIT_EXPIRED = "The Assessment submission time-period is over! Assessment can't be submitted"; + public static final String ASSESSMENT_ALREADY_SUBMITTED = "This Assessment is already Submitted!"; + + public static final String ASSESSMENT_SUBMIT_INVALID_QUESTION = "The QuestionId provided don't match to the Assessment Read"; + public static final String ASSESSMENT_SUBMIT_QUESTION_READ_FAILED = "Failed to read Question Set from DB"; + + + public static final String ASSESSMENT_READ_RESPONSE = "assessmentreadresponse"; + public static final String API_SUBMIT_ASSESSMENT = "api.submit.asssessment"; + public static final String MAX_ASSESSMENT_RETAKE_ATTEMPTS = "maxAssessmentRetakeAttempts"; + public static final String TOTAL_RETAKE_ATTEMPTS_ALLOWED = "attemptsAllowed"; + public static final String RETAKE_ATTEMPTS_CONSUMED = "attemptsMade"; + public static final String API_RETAKE_ASSESSMENT_GET = "api.assessmment.attempt"; public static final List USER_ENROLMENT_REPORT_FIELDS = Arrays.asList(USER_ID, FIRSTNAME, LASTNAME, EMAIL, PHONE, ROOT_ORG_ID, CHANNEL); @@ -566,7 +618,6 @@ public class Constants { COURSE_ORG_NAME); public static final List USER_ENROLMENT_COMMON_FIELDS = Arrays.asList(STATUS, COMPLETION_PERCENTAGE); - private Constants() { throw new IllegalStateException("Utility class"); }