package qa.qcri.aidr.trainer.pybossa.service.impl; import au.com.bytecode.opencsv.CSVParser; import org.apache.log4j.Logger; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import qa.qcri.aidr.trainer.pybossa.dao.TaskTranslationDao; import qa.qcri.aidr.trainer.pybossa.entity.*; import qa.qcri.aidr.trainer.pybossa.format.impl.TranslationRequestModel; import qa.qcri.aidr.trainer.pybossa.format.impl.TranslationResponseModel; import qa.qcri.aidr.trainer.pybossa.service.*; import qa.qcri.aidr.trainer.pybossa.store.PybossaConf; import qa.qcri.aidr.trainer.pybossa.store.LookupCode; import qa.qcri.aidr.trainer.pybossa.store.URLPrefixCode; import java.io.BufferedReader; import java.io.StringReader; import java.util.*; /** * Created by kamal on 3/22/15. */ @Service("translationService") @Transactional(readOnly = false) public class TWBTranslationServiceImpl implements TranslationService { @Autowired private TaskTranslationDao taskTranslationDao; @Autowired private SessionFactory sessionFactory; @Autowired private ReportTemplateService reportTemplateService; @Autowired private ClientAppResponseService clientAppResponseService; @Autowired private ClientAppService clientAppService; @Autowired private ClientAppEventService clientAppEventService; final private static int MAX_BATCH_SIZE = 1000; // final private static long MAX_WAIT_TIME_MILLIS = 172800000; // 48 hours final private static long MAX_CHECK_TIME_MILLIS = 43200000; // 12 hours private static long timeOfLastTranslationProcessingMillis = System.currentTimeMillis(); //initialize at startup private PybossaCommunicator pybossaCommunicator = new PybossaCommunicator(); protected static Logger logger = Logger.getLogger("service.translationService"); public Map processTranslations(ClientApp clientApp) { Long tcProjectId = clientApp.getTcProjectId(); pullAllTranslationResponses(clientApp.getClientAppID(), tcProjectId); return pushAllTranslations(clientApp.getClientAppID(), tcProjectId, MAX_WAIT_TIME_MILLIS, MAX_BATCH_SIZE); } public void processReceivedTranslations(Long clientAppId, int maxBatchSize){ List<TaskTranslation> translations = findAllTranslationsByClientAppIdAndStatus(clientAppId, TaskTranslation.STATUS_RECEIVED, maxBatchSize); for (TaskTranslation t : translations){ processMicroMappers(t, t.getAnswerCode()); } } public Map pushAllTranslations(Long clientAppId, Long twbProjectId, long maxTimeToWait, int maxBatchSize) { //add ordering List<TaskTranslation> translations = findAllTranslationsByClientAppIdAndStatus(clientAppId, TaskTranslation.STATUS_NEW, maxBatchSize); Map result = null; boolean forceProcessingByTime = false; long currentTimeMillis = System.currentTimeMillis(); // every 12hours // if ((currentTimeMillis - timeOfLastTranslationProcessingMillis) >= MAX_CHECK_TIME_MILLIS) { Calendar calendar = Calendar.getInstance(); int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK); if(dayOfWeek == Calendar.MONDAY || dayOfWeek == Calendar.WEDNESDAY || dayOfWeek == Calendar.FRIDAY ) { forceProcessingByTime = true; } // } if ((forceProcessingByTime || translations.size() >= maxBatchSize) && (translations.size() > 0)) { while (true) { logger.info("pushAllTranslations start at : " + new Date()); TranslationRequestModel model = new TranslationRequestModel(); model.setContactEmail("test@test.com"); model.setTitle("Translation Request from Micromappers"); model.setSourceLanguage("und"); String[] targets = {"eng"}; model.setTargetLanguages(targets); model.setSourceWordCount(100); //random test model.setInstructions("Please translate according to ..."); model.setDeadline(new Date(System.currentTimeMillis() + PybossaConf.TWB_TRANSLATE_DEADLINE)); model.setUrgency("high"); model.setProjectId(twbProjectId); model.setCallbackURL("https://www.example.com/my-callback-url"); model.setTranslationList(translations); result = pushTranslationRequest(model); if (result.get("order_id") != null) { Long orderId = new Long((Integer) result.get("order_id")); updateTranslationsWithOrderId(model.getTranslationList(), orderId); } translations = findAllTranslationsByClientAppIdAndStatus(clientAppId, TaskTranslation.STATUS_NEW, maxBatchSize); if (translations.size() < maxBatchSize) { break; } } } return result; } private void updateTranslationsWithOrderId(List<TaskTranslation> translations, Long orderId) { Iterator<TaskTranslation> itr = translations.iterator(); while (itr.hasNext()) { TaskTranslation translation = itr.next(); translation.setTwbOrderId(orderId); translation.setStatus(TaskTranslation.STATUS_IN_PROGRESS); updateTranslation(translation); } } public Map pushTranslationRequest(TranslationRequestModel request) { return TranslationCenterCommunicator.pushTranslationRequest(request); } public Map pushDocumentForRequest(TranslationRequestModel request) { return TranslationCenterCommunicator.pushDocumentForRequest(request); } public String pullAllTranslationResponses(Long clientAppId, Long twbProjectId) { List<Map> translationResponses = TranslationCenterCommunicator.pullAllTranslationResponses(clientAppId, twbProjectId); try { processTranslationResponses(translationResponses); } catch (Exception exception) { logger.debug("Exception caught: " + exception.toString()); } return null; } private String processTranslationResponses(List<Map> translationResponses) { boolean error = false; String errorMessage = ""; Iterator<Map> iterator = translationResponses.iterator(); while (iterator.hasNext()) { try { Map response = iterator.next(); Integer orderId = (Integer) response.get("order_id"); Integer projectId = (Integer) response.get("project_id"); List documents = (List) response.get("delivered_documents"); if (documents.size() > 0) { Map document = (Map) documents.get(documents.size() - 1); processTranslationDocument((String) document.get("download_link"), (String) document.get("self_link"), orderId, projectId); } else { throw new RuntimeException("No documents were found for order id: " + orderId + ", project id:" + projectId); } } catch (Exception ex) { logger.debug(" processTranslationResponses : " + ex.toString()); } } return null; } private void processTranslationDocument(String download_link, String selfLink, Integer orderId, Integer projectId) throws Exception { try { int translationCount = taskTranslationDao.countAllTranslationsByOrderID(orderId); if(translationCount > 0 ) { String content = TranslationCenterCommunicator.getTranslationDocumentContent(download_link); processResponseDocumentContent(content, orderId, projectId); TranslationCenterCommunicator.updateTranslationOrder(selfLink, "accepted", "Translation was accepted"); } } catch (Exception exception) { logger.debug("Exception caught: " + exception.toString()); TranslationCenterCommunicator.updateTranslationOrder(selfLink, "rejected", exception.toString()); } } @Transactional private void processResponseDocumentContent(String content, Integer orderId, Integer projectId) throws Exception { BufferedReader reader = new BufferedReader(new StringReader(content)); String line; String[] toks; CSVParser parser = new CSVParser(); int counter = 1; reader.readLine(); //skip the first line which is a header. while ((line = reader.readLine()) != null) { counter++; line = line.trim(); if (line.length() <= 0) continue; try { toks = parser.parseLine(line); if (toks.length != 4) { throw new RuntimeException("Invalid number of columns in row " + counter); } updateTranslation(orderId, new Long(toks[0]), toks[1], toks[2], toks[3]); } catch (Exception e) { logger.error("Invalid line: " + line + " (" + e.getMessage() + ")"); throw new RuntimeException("Invalid line: " + line + " (" + e.getMessage() + ")"); } } } public void updateTranslation(Integer orderId, Long taskId, String sourceTranslation, String finalTranslation, String code) throws Exception { TaskTranslation taskTranslation = findByTaskId(taskId); if (taskTranslation != null) { if(taskTranslation.getStatus().equalsIgnoreCase(TaskTranslation.STATUS_COMPLETE)){ return; } if(taskTranslation.getStatus().equalsIgnoreCase(TaskTranslation.STATUS_RECEIVED)){ this.processMicroMappers( taskTranslation, code); return; } } if (taskTranslation == null) { logger.error("No translation task found for id:" + taskId); //return; throw new RuntimeException("No translation task found for id:" + taskId); } else if (taskTranslation.getTwbOrderId() == null) { logger.error("No TWB order number found for id:" + taskId); throw new RuntimeException("No TWB order number found for id:" + taskId); } else if (taskTranslation.getTwbOrderId().intValue() != orderId.intValue()) { logger.error("TWB order number does not match : TwbOrderId: " + taskTranslation.getTwbOrderId().intValue() + " - Order ID : " + orderId.intValue()); throw new RuntimeException("TWB order number does not match : TwbOrderId: " + taskTranslation.getTwbOrderId().intValue() + " - Order ID : " + orderId.intValue()); } taskTranslation.setTranslatedText(finalTranslation); taskTranslation.setAnswerCode(code); taskTranslation.setStatus(TaskTranslation.STATUS_RECEIVED); updateTranslation(taskTranslation); this.processMicroMappers(taskTranslation, code); } private void processMicroMappers(TaskTranslation taskTranslation, String code){ try{ if(code.equals(null) && !taskTranslation.getAnswerCode().equals(null)){ code = taskTranslation.getAnswerCode(); } if(taskTranslation.getDocumentId() == null){ List<TaskQueueResponse> taskResp = clientAppResponseService.getTaskQueueResponse(taskTranslation.getTaskQueueID()); if(taskResp.size() > 0){ String taskInfo = taskResp.get(0).getTaskInfo(); JSONParser parser = new JSONParser(); JSONArray jsonObject = (JSONArray) parser.parse(taskInfo); Iterator itr= jsonObject.iterator(); JSONArray jsonObjectCopy = new JSONArray(); while(itr.hasNext()){ JSONObject featureJsonObj = (JSONObject)itr.next(); JSONObject info = (JSONObject)featureJsonObj.get("info"); info.put("category", code); jsonObjectCopy.add(featureJsonObj) ; } this.processAIDRPushing(taskTranslation, jsonObjectCopy); } } else{ ClientApp app = clientAppService.findClientAppByID("clientAppID", taskTranslation.getClientAppId()); TranslationResponseModel taskRespModel = new TranslationResponseModel(taskTranslation,app,code); this.processAIDRPushing(taskTranslation, taskRespModel.getTaskResponse()); } this.processReportTemplatePushing(taskTranslation, code); } catch(Exception e){ logger.error("processAIDR:" + e); throw new RuntimeException("processAIDR:" + e); } } private void processReportTemplatePushing(TaskTranslation taskTranslation, String userAnswer){ try{ if(clientAppEventService.getNextSequenceClientAppEvent(taskTranslation.getClientAppId()) != null){ JSONParser parser = new JSONParser(); ClientAppAnswer clientAppAnswer = clientAppResponseService.getClientAppAnswer(taskTranslation.getClientAppId()); String[] activeAnswers = this.getActiveAnswerKey(clientAppAnswer, parser) ; for(int a=0; a < activeAnswers.length; a++){ if(activeAnswers[a].equalsIgnoreCase(userAnswer)){ ReportTemplate template = new ReportTemplate(taskTranslation.getTaskQueueID(), taskTranslation.getTaskId(), taskTranslation.getTweetID(), taskTranslation.getTranslatedText(), taskTranslation.getAuthor(), taskTranslation.getLat(), taskTranslation.getLon(), taskTranslation.getUrl(), taskTranslation.getCreated().toString(), taskTranslation.getAnswerCode(), LookupCode.TEMPLATE_IS_READY_FOR_EXPORT, taskTranslation.getClientAppId()); reportTemplateService.saveReportItem(template); } } } } catch(Exception e){ throw new RuntimeException("TWB processReportTemplatePushing"); } } private void processAIDRPushing(TaskTranslation taskTranslation, JSONArray jsonObjectCopy) { long appID = taskTranslation.getClientAppId(); ClientApp app = clientAppService.findClientAppByID("clientAppID", appID); String AIDR_TASK_ANSWER_URL = app.getClient().getAidrHostURL() + URLPrefixCode.TASK_ANSWER_SAVE; pybossaCommunicator.sendPost(jsonObjectCopy.toJSONString(), AIDR_TASK_ANSWER_URL); taskTranslation.setStatus(TaskTranslation.STATUS_COMPLETE); updateTranslation(taskTranslation); } private String[] getActiveAnswerKey(ClientAppAnswer clientAppAnswer, JSONParser parser) throws ParseException { String answerKey = clientAppAnswer.getActiveAnswerKey(); if(answerKey== null){ answerKey = clientAppAnswer.getAnswer(); } JSONArray questionArrary = (JSONArray) parser.parse(answerKey) ; int questionSize = questionArrary.size(); String[] questions = new String[questionSize]; for(int i=0; i< questionSize; i++){ JSONObject obj = (JSONObject)questionArrary.get(i); questions[i] = (String)obj.get("qa"); } return questions; } @Override @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW) public void createTranslation(TaskTranslation translation) { taskTranslationDao.createTaskTranslation(translation); Session session = sessionFactory.getCurrentSession(); session.flush(); } @Override @Transactional public void updateTranslation(TaskTranslation translation) { taskTranslationDao.saveOrUpdateTaskTranslation(translation); } @Override @Transactional public TaskTranslation findById(Long translationId) { return taskTranslationDao.findTranslationByID(translationId); } @Transactional public TaskTranslation findByTaskId(Long taskId) { TaskTranslation taskTranslation = null; try{ taskTranslation = taskTranslationDao.findTranslationByTaskID(taskId); if(taskTranslation.getTaskId() == null){ taskTranslation = null; } } catch(Exception e){ List<TaskTranslation> translations = taskTranslationDao.findAllTranslationsByTaskID(taskId); if(translations.size() > 0) taskTranslation = translations.get(0); } logger.debug("findByTaskId : taskTranslation " + taskTranslation); return taskTranslation; } @Override @Transactional public void delete(TaskTranslation translation) { taskTranslationDao.delete(translation); } @Override @Transactional public List<TaskTranslation> findAllTranslations() { return taskTranslationDao.findAllTranslations(); } @Transactional public List<TaskTranslation> findAllTranslationsByClientAppIdAndStatus(Long clientAppId, String status, Integer count) { return taskTranslationDao.findAllTranslationsByClientAppIdAndStatus(clientAppId, status, count); } }