Java tutorial
package sg.ncl; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.IOUtils; import org.apache.tomcat.util.codec.binary.Base64; import org.json.JSONArray; import org.json.JSONObject; import org.springframework.http.*; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; import org.springframework.validation.ObjectError; import org.springframework.web.bind.annotation.*; import org.springframework.web.client.RequestCallback; import org.springframework.web.client.ResponseExtractor; import org.springframework.web.servlet.mvc.support.RedirectAttributes; import sg.ncl.domain.ExceptionState; import sg.ncl.exceptions.WebServiceRuntimeException; import sg.ncl.testbed_interface.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import javax.validation.Valid; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.util.*; import static sg.ncl.domain.ExceptionState.*; /** * Created by dcsjnh on 11/17/2016. */ @Controller @RequestMapping("/data") @Slf4j public class DataController extends MainController { private static final String REDIRECT_DATA = "redirect:/data"; private static final String REDIRECT_CONTRIBUTE = "redirect:/data/contribute/"; private static final String CATEGORIES = "categories"; private static final String LICENSES = "licenses"; private static final String RESOURCES = "resources"; private static final String DATASET = "dataset"; private static final String CONTRIBUTE_DATA_PAGE = "data_contribute"; private static final String MESSAGE_ATTRIBUTE = "message"; private static final String PUBLIC_USER_ID = "publicUserId"; private static final String EDITABLE_FLAG = "editable"; private static final String START_DATE = "startDate="; private static final String END_DATE = "endDate="; private static final String DATA_ID = "dataId"; private static final String ERRORS_STR = "Error(s):"; private static final String UL_TAG_START = "<ul class=\"fa-ul\">"; private static final String LI_START_TAG = "<li><i class=\"fa fa-exclamation-circle\"></i> "; private static final String LI_END_TAG = "</li>"; private static final String UL_END_TAG = "</ul>"; @RequestMapping public String data(Model model) { DatasetManager datasetManager = new DatasetManager(); HttpEntity<String> request = createHttpEntityHeaderOnly(); ResponseEntity response = restTemplate.exchange(properties.getData(), HttpMethod.GET, request, String.class); String dataResponseBody = response.getBody().toString(); log.info("data: {}", dataResponseBody); JSONArray dataJsonArray = new JSONArray(dataResponseBody); for (int i = 0; i < dataJsonArray.length(); i++) { JSONObject dataInfoObject = dataJsonArray.getJSONObject(i); Dataset dataset = extractDataInfo(dataInfoObject.toString()); datasetManager.addDataset(dataset); } model.addAttribute(CATEGORIES, getDataCategories()); model.addAttribute(LICENSES, getDataLicenses()); model.addAttribute("allDataMap", datasetManager.getDatasetMap()); return "data"; } @RequestMapping(value = "/search", method = RequestMethod.GET) public String searchData(Model model, @RequestParam("keywords") String keywords) { if (keywords.trim().length() == 0) { return REDIRECT_DATA; } DatasetManager datasetManager = new DatasetManager(); HttpEntity<String> request = createHttpEntityHeaderOnly(); ResponseEntity response = restTemplate.exchange(properties.searchDatasets(keywords), HttpMethod.GET, request, String.class); String dataResponseBody = response.getBody().toString(); JSONArray dataJsonArray = new JSONArray(dataResponseBody); for (int i = 0; i < dataJsonArray.length(); i++) { JSONObject dataInfoObject = dataJsonArray.getJSONObject(i); Dataset dataset = extractDataInfo(dataInfoObject.toString()); datasetManager.addDataset(dataset); } model.addAttribute(CATEGORIES, getDataCategories()); model.addAttribute(LICENSES, getDataLicenses()); model.addAttribute("allDataMap", datasetManager.getDatasetMap()); model.addAttribute("requestForm", new DataRequestForm()); model.addAttribute("keywords", keywords); return "data"; } @RequestMapping(value = { "/contribute", "/contribute/{id}" }, method = RequestMethod.GET) public String contributeData(Model model, @PathVariable Optional<String> id, HttpSession session) { if (id.isPresent()) { Dataset dataset = getDataset(id.get()); if (dataset.getContributorId().equals(session.getAttribute("id").toString())) { model.addAttribute(EDITABLE_FLAG, true); } model.addAttribute(DATASET, dataset); model.addAttribute("data", dataset); } else { model.addAttribute(EDITABLE_FLAG, true); model.addAttribute(DATASET, new Dataset()); } model.addAttribute(CATEGORIES, getDataCategories()); model.addAttribute(LICENSES, getDataLicenses()); model.addAttribute("requestForm", new DataRequestForm()); model.addAttribute("agreementForm", new LicenseAgreementForm()); return CONTRIBUTE_DATA_PAGE; } private Dataset getDataset(String id) { HttpEntity<String> request = createHttpEntityHeaderOnly(); ResponseEntity response = restTemplate.exchange(properties.getDataset(id), HttpMethod.GET, request, String.class); String dataResponseBody = response.getBody().toString(); JSONObject dataInfoObject = new JSONObject(dataResponseBody); return extractDataInfo(dataInfoObject.toString()); } @RequestMapping(value = "/licensesInfo") public String getLicensesInfo(Model model) { model.addAttribute(LICENSES, getDataLicenses()); return "data_licenses_info"; } private List<DataCategory> getDataCategories() { HttpEntity<String> request = createHttpEntityHeaderOnly(); ResponseEntity response = restTemplate.exchange(properties.getCategories(), HttpMethod.GET, request, String.class); String responseBody = response.getBody().toString(); JSONArray jsonArray = new JSONArray(responseBody); List<DataCategory> dataCategories = new ArrayList<>(); for (int i = 0; i < jsonArray.length(); i++) { JSONObject jsonObject = jsonArray.getJSONObject(i); DataCategory dataCategory = extractCategoryInfo(jsonObject.toString()); dataCategories.add(dataCategory); } return dataCategories; } private List<DataLicense> getDataLicenses() { HttpEntity<String> request = createHttpEntityHeaderOnly(); ResponseEntity response = restTemplate.exchange(properties.getLicenses(), HttpMethod.GET, request, String.class); String responseBody = response.getBody().toString(); JSONArray jsonArray = new JSONArray(responseBody); List<DataLicense> dataLicenses = new ArrayList<>(); for (int i = 0; i < jsonArray.length(); i++) { JSONObject jsonObject = jsonArray.getJSONObject(i); DataLicense dataLicense = extractLicenseInfo(jsonObject.toString()); dataLicenses.add(dataLicense); } return dataLicenses; } @RequestMapping(value = { "/contribute", "/contribute/{id}" }, method = RequestMethod.POST) public String validateContributeData(@Valid @ModelAttribute("dataset") Dataset dataset, BindingResult bindingResult, Model model, @PathVariable Optional<String> id, HttpSession session) throws WebServiceRuntimeException { setContributor(dataset, session); if (bindingResult.hasErrors()) { StringBuilder message = new StringBuilder(); message.append(ERRORS_STR); message.append(UL_TAG_START); for (ObjectError objectError : bindingResult.getAllErrors()) { FieldError fieldError = (FieldError) objectError; message.append(LI_START_TAG); switch (fieldError.getField()) { case "categoryId": message.append("category must be selected"); break; case "licenseId": message.append("license must be selected"); break; default: message.append(fieldError.getField()); message.append(" "); message.append(fieldError.getDefaultMessage()); } message.append(LI_END_TAG); } message.append(UL_END_TAG); model.addAttribute(MESSAGE_ATTRIBUTE, message.toString()); model.addAttribute(CATEGORIES, getDataCategories()); model.addAttribute(LICENSES, getDataLicenses()); if (id.isPresent()) { Dataset data = getDataset(id.get()); model.addAttribute("data", data); } model.addAttribute(EDITABLE_FLAG, true); return CONTRIBUTE_DATA_PAGE; } JSONObject dataObject = new JSONObject(); dataObject.put("name", dataset.getName()); dataObject.put("description", dataset.getDescription()); dataObject.put("contributorId", dataset.getContributorId()); dataObject.put("visibility", dataset.getVisibility()); dataObject.put("accessibility", dataset.getAccessibility()); dataObject.put("resources", new ArrayList()); dataObject.put("approvedUsers", new ArrayList()); dataObject.put("releasedDate", dataset.getReleasedDate()); dataObject.put("categoryId", dataset.getCategoryId()); dataObject.put("licenseId", dataset.getLicenseId()); dataObject.put("keywords", dataset.getKeywordList()); log.debug("DataObject: {}", dataObject.toString()); HttpEntity<String> request = createHttpEntityWithBody(dataObject.toString()); restTemplate.setErrorHandler(new MyResponseErrorHandler()); ResponseEntity response = getResponseEntity(id, request); String dataResponseBody = response.getBody().toString(); try { if (RestUtil.isError(response.getStatusCode())) { MyErrorResource error = objectMapper.readValue(dataResponseBody, MyErrorResource.class); ExceptionState exceptionState = ExceptionState.parseExceptionState(error.getError()); checkExceptionState(dataset, model, exceptionState); return CONTRIBUTE_DATA_PAGE; } } catch (IOException e) { log.error("validateContributeData: {}", e.toString()); throw new WebServiceRuntimeException(e.getMessage()); } log.info("Dataset saved: {}", dataResponseBody); return REDIRECT_DATA; } private void checkExceptionState(@Valid @ModelAttribute("dataset") Dataset dataset, Model model, ExceptionState exceptionState) { switch (exceptionState) { case DATA_NAME_ALREADY_EXISTS_EXCEPTION: log.warn("Dataset name already exists: {}", dataset.getName()); model.addAttribute(MESSAGE_ATTRIBUTE, "Error(s):<ul><li>dataset name already exists</li></ul>"); break; case DATA_NOT_FOUND_EXCEPTION: log.warn("Dataset not found for updating."); model.addAttribute(MESSAGE_ATTRIBUTE, "Error(s):<ul><li>dataset not found for editing</li></ul>"); break; case FORBIDDEN_EXCEPTION: log.warn("Saving of dataset forbidden."); model.addAttribute(MESSAGE_ATTRIBUTE, "Error(s):<ul><li>saving dataset forbidden</li></ul>"); break; default: log.warn("Unknown error for validating data contribution."); model.addAttribute(MESSAGE_ATTRIBUTE, "Error(s):<ul><li>unknown error for validating data contribution</li></ul>"); } } @RequestMapping(value = { "/remove/{id}", "/remove/{id}/{admin}" }) public String removeDataset(@PathVariable String id, @PathVariable Optional<String> admin, RedirectAttributes redirectAttributes) throws WebServiceRuntimeException { HttpEntity<String> request = createHttpEntityHeaderOnly(); restTemplate.setErrorHandler(new MyResponseErrorHandler()); ResponseEntity response = restTemplate.exchange(properties.getDataset(id), HttpMethod.DELETE, request, String.class); String dataResponseBody = response.getBody().toString(); try { if (RestUtil.isError(response.getStatusCode())) { MyErrorResource error = objectMapper.readValue(dataResponseBody, MyErrorResource.class); ExceptionState exceptionState = ExceptionState.parseExceptionState(error.getError()); if (exceptionState == FORBIDDEN_EXCEPTION) { log.warn("Removing of dataset forbidden."); redirectAttributes.addFlashAttribute(MESSAGE_ATTRIBUTE, error.getMessage()); } else if (exceptionState == DATA_NOT_FOUND_EXCEPTION) { log.warn("Dataset not found for removing."); redirectAttributes.addFlashAttribute(MESSAGE_ATTRIBUTE, error.getMessage()); } else { log.warn("Unknown error for removing dataset."); } } else { log.info("Dataset removed: {}", dataResponseBody); } } catch (IOException e) { log.error("removeDataset: {}", e.toString()); throw new WebServiceRuntimeException(e.getMessage()); } if (admin.isPresent()) { return "redirect:/admin/data"; } return REDIRECT_DATA; } @RequestMapping(value = "/{id}/request", method = RequestMethod.POST) public String requestDataset(@PathVariable String id, @ModelAttribute DataRequestForm requestForm, RedirectAttributes redirectAttributes) throws WebServiceRuntimeException { JSONObject requestObject = new JSONObject(); requestObject.put("reason", requestForm.getReason()); HttpEntity<String> request = createHttpEntityWithBody(requestObject.toString()); restTemplate.setErrorHandler(new MyResponseErrorHandler()); ResponseEntity response = restTemplate.exchange(properties.requestDataset(id), HttpMethod.POST, request, String.class); String dataResponseBody = response.getBody().toString(); try { if (RestUtil.isError(response.getStatusCode())) { MyErrorResource error = objectMapper.readValue(dataResponseBody, MyErrorResource.class); ExceptionState exceptionState = ExceptionState.parseExceptionState(error.getError()); if (exceptionState == FORBIDDEN_EXCEPTION) { log.warn("Requesting of dataset access forbidden."); redirectAttributes.addFlashAttribute(MESSAGE_ATTRIBUTE, error.getMessage()); } else if (exceptionState == DATA_NOT_FOUND_EXCEPTION) { log.warn("Dataset not found for requesting access."); redirectAttributes.addFlashAttribute(MESSAGE_ATTRIBUTE, error.getMessage()); } else { log.warn("Unknown error for requesting dataset access."); } } else { log.info("Dataset access requested: {}", dataResponseBody); redirectAttributes.addFlashAttribute("success", true); } } catch (IOException e) { log.error("requestDataset: {}", e.toString()); throw new WebServiceRuntimeException(e.getMessage()); } return REDIRECT_CONTRIBUTE + id; } @RequestMapping(value = "{did}/requests/{rid}", method = RequestMethod.GET) public String getRequest(Model model, @PathVariable String did, @PathVariable String rid) throws WebServiceRuntimeException { HttpEntity<String> request = createHttpEntityHeaderOnly(); restTemplate.setErrorHandler(new MyResponseErrorHandler()); ResponseEntity response = restTemplate.exchange(properties.getRequest(did, rid), HttpMethod.GET, request, String.class); String dataResponseBody = response.getBody().toString(); try { if (RestUtil.isError(response.getStatusCode())) { MyErrorResource error = objectMapper.readValue(dataResponseBody, MyErrorResource.class); ExceptionState exceptionState = ExceptionState.parseExceptionState(error.getError()); if (exceptionState == FORBIDDEN_EXCEPTION) { log.warn("Requesting of dataset access forbidden."); model.addAttribute(MESSAGE_ATTRIBUTE, error.getMessage()); } else if (exceptionState == DATA_ACCESS_REQUEST_NOT_FOUND_EXCEPTION) { log.warn("Dataset access request not found."); model.addAttribute(MESSAGE_ATTRIBUTE, error.getMessage()); } else if (exceptionState == DATA_NOT_MATCH_EXCEPTION) { log.warn("Dataset access request does not have matching parent."); model.addAttribute(MESSAGE_ATTRIBUTE, error.getMessage()); } else if (exceptionState == DATA_NOT_FOUND_EXCEPTION) { log.warn("Dataset not found for requesting access."); model.addAttribute(MESSAGE_ATTRIBUTE, error.getMessage()); } else { log.warn("Unknown error for getting dataset access request."); } } else { log.info("Dataset access request retrieved: {}", dataResponseBody); DataAccessRequest dataAccessRequest = extractDataAccessRequest(dataResponseBody); model.addAttribute("dataAccessRequest", dataAccessRequest); } } catch (IOException e) { log.error("getRequest: {}", e.toString()); throw new WebServiceRuntimeException(e.getMessage()); } return "data_request_access"; } @RequestMapping("{did}/requests/{rid}/approve") public String approveRequest(@PathVariable String did, @PathVariable String rid, RedirectAttributes redirectAttributes) throws WebServiceRuntimeException { HttpEntity<String> request = createHttpEntityHeaderOnly(); restTemplate.setErrorHandler(new MyResponseErrorHandler()); ResponseEntity response = restTemplate.exchange(properties.getRequest(did, rid), HttpMethod.PUT, request, String.class); String dataResponseBody = response.getBody().toString(); try { if (RestUtil.isError(response.getStatusCode())) { MyErrorResource error = objectMapper.readValue(dataResponseBody, MyErrorResource.class); ExceptionState exceptionState = ExceptionState.parseExceptionState(error.getError()); if (exceptionState == FORBIDDEN_EXCEPTION) { log.warn("Approving of dataset access request forbidden."); redirectAttributes.addFlashAttribute(MESSAGE_ATTRIBUTE, error.getMessage()); } else if (exceptionState == DATA_ACCESS_REQUEST_NOT_FOUND_EXCEPTION) { log.warn("Dataset access request not found."); redirectAttributes.addFlashAttribute(MESSAGE_ATTRIBUTE, error.getMessage()); } else if (exceptionState == DATA_NOT_MATCH_EXCEPTION) { log.warn("Dataset access request does not have matching parent."); redirectAttributes.addFlashAttribute(MESSAGE_ATTRIBUTE, error.getMessage()); } else if (exceptionState == DATA_NOT_FOUND_EXCEPTION) { log.warn("Dataset not found for approving request."); redirectAttributes.addFlashAttribute(MESSAGE_ATTRIBUTE, error.getMessage()); } else { log.warn("Unknown error for approving dataset access request."); } } else { log.info("Dataset access request approved: {}", dataResponseBody); redirectAttributes.addFlashAttribute("approved_message", "Request Approved"); } } catch (IOException e) { log.error("approveRequest: {}", e.toString()); throw new WebServiceRuntimeException(e.getMessage()); } return "redirect:/data/" + did + "/requests/" + rid; } @RequestMapping(value = "/public", method = RequestMethod.GET) public String getPublicDatasets(Model model) { DatasetManager datasetManager = new DatasetManager(); HttpEntity<String> dataRequest = createHttpEntityHeaderOnlyNoAuthHeader(); ResponseEntity dataResponse = restTemplate.exchange(properties.getPublicData(), HttpMethod.GET, dataRequest, String.class); String dataResponseBody = dataResponse.getBody().toString(); JSONArray dataPublicJsonArray = new JSONArray(dataResponseBody); for (int i = 0; i < dataPublicJsonArray.length(); i++) { JSONObject dataInfoObject = dataPublicJsonArray.getJSONObject(i); Dataset dataset = extractDataInfo(dataInfoObject.toString()); datasetManager.addDataset(dataset); } model.addAttribute("publicDataMap", datasetManager.getDatasetMap()); return "data_public"; } @RequestMapping(value = "/public/{id}", method = RequestMethod.GET) public String getPublicDataset(HttpSession session, Model model, @PathVariable String id) { if (session.getAttribute("id") != null && !session.getAttribute("id").toString().isEmpty()) { return REDIRECT_CONTRIBUTE + id; } HttpEntity<String> dataRequest = createHttpEntityHeaderOnlyNoAuthHeader(); ResponseEntity dataResponse = restTemplate.exchange(properties.getPublicDataset(id), HttpMethod.GET, dataRequest, String.class); String dataResponseBody = dataResponse.getBody().toString(); JSONObject dataInfoObject = new JSONObject(dataResponseBody); Dataset dataset = extractDataInfo(dataInfoObject.toString()); model.addAttribute(DATASET, dataset); model.addAttribute("puser", new PublicUser()); return "data_public_id"; } @RequestMapping(value = "/public/{id}", method = RequestMethod.POST) public String checkPublicDataset(HttpSession session, Model model, @PathVariable String id, @Valid @ModelAttribute("puser") PublicUser puser, BindingResult bindingResult) { if (bindingResult.hasErrors()) { StringBuilder message = new StringBuilder(); message.append(ERRORS_STR); message.append(UL_TAG_START); for (ObjectError objectError : bindingResult.getAllErrors()) { FieldError fieldError = (FieldError) objectError; message.append(LI_START_TAG); switch (fieldError.getField()) { case "fullName": message.append("You have to fill in your full name"); break; case "email": message.append("You have to fill in your email address"); break; case "jobTitle": message.append("You have to fill in your job title"); break; case "institution": message.append("You have to fill in your institution"); break; case "country": message.append("You have to fill in your country"); break; case "licenseAgreed": message.append("You have to agree to the licensing terms"); break; default: message.append(fieldError.getField()); message.append(" "); message.append(fieldError.getDefaultMessage()); } message.append(LI_END_TAG); } message.append(UL_END_TAG); model.addAttribute(MESSAGE_ATTRIBUTE, message); HttpEntity<String> dataRequest = createHttpEntityHeaderOnlyNoAuthHeader(); ResponseEntity dataResponse = restTemplate.exchange(properties.getPublicDataset(id), HttpMethod.GET, dataRequest, String.class); String dataResponseBody = dataResponse.getBody().toString(); JSONObject dataInfoObject = new JSONObject(dataResponseBody); Dataset dataset = extractDataInfo(dataInfoObject.toString()); model.addAttribute(DATASET, dataset); return "data_public_id"; } JSONObject puserObject = new JSONObject(); puserObject.put("fullName", puser.getFullName()); puserObject.put("email", puser.getEmail()); puserObject.put("jobTitle", puser.getJobTitle()); puserObject.put("institution", puser.getInstitution()); puserObject.put("country", puser.getCountry()); puserObject.put("licenseAgreed", puser.isLicenseAgreed()); HttpEntity<String> request = createHttpEntityWithBodyNoAuthHeader(puserObject.toString()); restTemplate.setErrorHandler(new MyResponseErrorHandler()); ResponseEntity response = restTemplate.exchange(properties.getPublicDataUsers(), HttpMethod.POST, request, String.class); String responseBody = response.getBody().toString(); log.info("Public user saved: {}", responseBody); JSONObject object = new JSONObject(responseBody); session.setAttribute(PUBLIC_USER_ID, object.getLong("id")); return REDIRECT_DATA + "/public/" + id + "/" + RESOURCES; } @RequestMapping(value = "/public/{id}/resources", method = RequestMethod.GET) public String getPublicResources(HttpSession session, Model model, @PathVariable String id, RedirectAttributes redirectAttributes) { if (session.getAttribute("id") != null && !session.getAttribute("id").toString().isEmpty()) { return REDIRECT_DATA + "/" + id + "/" + RESOURCES; } else if (session.getAttribute(PUBLIC_USER_ID) == null || session.getAttribute(PUBLIC_USER_ID).toString().isEmpty()) { redirectAttributes.addFlashAttribute(MESSAGE_ATTRIBUTE, "Please fill in your details before trying to access resources."); return REDIRECT_DATA + "/public/" + id; } HttpEntity<String> dataRequest = createHttpEntityHeaderOnlyNoAuthHeader(); ResponseEntity dataResponse = restTemplate.exchange(properties.getPublicDataset(id), HttpMethod.GET, dataRequest, String.class); String dataResponseBody = dataResponse.getBody().toString(); JSONObject dataInfoObject = new JSONObject(dataResponseBody); Dataset dataset = extractDataInfo(dataInfoObject.toString()); model.addAttribute(DATASET, dataset); return "data_public_resources"; } @RequestMapping(value = "/public/{did}/resources/{rid}", method = RequestMethod.GET) public void getPublicOpenResource(HttpSession session, @PathVariable String did, @PathVariable String rid, final HttpServletResponse httpResponse) throws UnsupportedEncodingException, WebServiceRuntimeException { if (session.getAttribute(PUBLIC_USER_ID) == null || session.getAttribute(PUBLIC_USER_ID).toString().isEmpty()) { throw new WebServiceRuntimeException("No public user id for downloading."); } try { String publicUserId = session.getAttribute(PUBLIC_USER_ID).toString(); // Optional Accept header RequestCallback requestCallback = request -> { request.getHeaders().setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL)); request.getHeaders().set("PublicUserId", publicUserId); }; // Streams the response instead of loading it all in memory ResponseExtractor<Void> responseExtractor = getResponseExtractor(httpResponse); restTemplate.execute(properties.downloadPublicOpenResource(did, rid), HttpMethod.GET, requestCallback, responseExtractor); } catch (Exception e) { log.error("Error transferring download: {}", e.getMessage()); } } private ResponseExtractor<Void> getResponseExtractor(final HttpServletResponse httpResponse) { return response -> { String content = response.getHeaders().get("Content-Disposition").get(0); httpResponse.setContentType("application/octet-stream"); httpResponse.setContentLengthLong(Long.parseLong(response.getHeaders().get("Content-Length").get(0))); httpResponse.setHeader("Content-Disposition", content); IOUtils.copy(response.getBody(), httpResponse.getOutputStream()); httpResponse.flushBuffer(); return null; }; } @RequestMapping("{datasetId}/resources") public String getResources(@PathVariable String datasetId, @ModelAttribute LicenseAgreementForm agreementForm, HttpSession session, Model model, RedirectAttributes redirectAttributes) { if (!agreementForm.isLicenseAgreed()) { StringBuilder message = new StringBuilder(); message.append(ERRORS_STR); message.append(UL_TAG_START); message.append(LI_START_TAG); message.append("You have to agree to the licensing terms"); message.append(LI_END_TAG); message.append(UL_END_TAG); redirectAttributes.addFlashAttribute(MESSAGE_ATTRIBUTE, message); redirectAttributes.addFlashAttribute("hasErrors", true); return REDIRECT_CONTRIBUTE + datasetId; } HttpEntity<String> request = createHttpEntityHeaderOnly(); ResponseEntity response = restTemplate.exchange(properties.getDataset(datasetId), HttpMethod.GET, request, String.class); String dataResponseBody = response.getBody().toString(); JSONObject dataInfoObject = new JSONObject(dataResponseBody); Dataset dataset = extractDataInfo(dataInfoObject.toString()); if (dataset.getContributorId().equals(session.getAttribute("id").toString())) { model.addAttribute(EDITABLE_FLAG, true); } model.addAttribute(DATASET, dataset); return "data_resources"; } @RequestMapping("{datasetId}/stats") public String getStatistics(Model model, @PathVariable String datasetId, @RequestParam(value = "start", required = false) String start, @RequestParam(value = "end", required = false) String end, HttpSession session) { if (!validateIfAdmin(session)) { return "nopermission"; } if (start == null) { start = ""; } if (end == null) { end = ""; } HttpEntity<String> request = createHttpEntityHeaderOnly(); ResponseEntity response = restTemplate.exchange(properties.getDataset(datasetId), HttpMethod.GET, request, String.class); String dataResponseBody = response.getBody().toString(); JSONObject dataInfoObject = new JSONObject(dataResponseBody); Dataset dataset = extractDataInfo(dataInfoObject.toString()); if (start.isEmpty() && end.isEmpty()) { response = restTemplate.exchange(properties.getDownloadStat("id=" + datasetId), HttpMethod.GET, request, String.class); } else if (end.isEmpty()) { response = restTemplate.exchange(properties.getDownloadStat("id=" + datasetId, START_DATE + start), HttpMethod.GET, request, String.class); } else if (start.isEmpty()) { response = restTemplate.exchange(properties.getDownloadStat("id=" + datasetId, END_DATE + end), HttpMethod.GET, request, String.class); } else { response = restTemplate.exchange( properties.getDownloadStat("id=" + datasetId, START_DATE + start, END_DATE + end), HttpMethod.GET, request, String.class); } String responseBody = response.getBody().toString(); Map<Integer, Long> downloadStats = new HashMap<>(); JSONArray statJsonArray = new JSONArray(responseBody); for (int i = 0; i < statJsonArray.length(); i++) { JSONObject statInfoObject = statJsonArray.getJSONObject(i); downloadStats.put(statInfoObject.getInt(DATA_ID), statInfoObject.getLong("count")); } if (start.isEmpty() && end.isEmpty()) { response = restTemplate.exchange(properties.getPublicDownloadStat("id=" + datasetId), HttpMethod.GET, request, String.class); } else if (end.isEmpty()) { response = restTemplate.exchange( properties.getPublicDownloadStat("id=" + datasetId, START_DATE + start), HttpMethod.GET, request, String.class); } else if (start.isEmpty()) { response = restTemplate.exchange(properties.getPublicDownloadStat("id=" + datasetId, END_DATE + end), HttpMethod.GET, request, String.class); } else { response = restTemplate.exchange( properties.getPublicDownloadStat("id=" + datasetId, START_DATE + start, END_DATE + end), HttpMethod.GET, request, String.class); } responseBody = response.getBody().toString(); Map<Integer, Long> publicDownloadStats = new HashMap<>(); statJsonArray = new JSONArray(responseBody); for (int i = 0; i < statJsonArray.length(); i++) { JSONObject statInfoObject = statJsonArray.getJSONObject(i); publicDownloadStats.put(statInfoObject.getInt(DATA_ID), statInfoObject.getLong("count")); } model.addAttribute(DATASET, dataset); model.addAttribute("downloadStats", downloadStats); model.addAttribute("publicDownloadStats", publicDownloadStats); model.addAttribute("start", start); model.addAttribute("end", end); return "data_statistics"; } /** * References: * [1] https://github.com/23/resumable.js/blob/master/samples/java/src/main/java/resumable/js/upload/UploadServlet.java */ @RequestMapping(value = "{datasetId}/resources/upload", method = RequestMethod.GET) public ResponseEntity<String> checkChunk(@PathVariable String datasetId, HttpServletRequest request) { int resumableChunkNumber = getResumableChunkNumber(request); ResumableInfo info = getResumableInfo(request); String url = properties.checkUploadChunk(datasetId, resumableChunkNumber, info.resumableIdentifier); log.debug("URL: {}", url); HttpEntity<String> httpEntity = createHttpEntityHeaderOnly(); ResponseEntity responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, String.class); String body = responseEntity.getBody().toString(); log.debug(body); if (body.equals("Not found")) { return ResponseEntity.status(HttpStatus.NOT_FOUND).body(body); } return ResponseEntity.ok(body); } @RequestMapping(value = "{datasetId}/resources/upload", method = RequestMethod.POST) public ResponseEntity<String> uploadChunk(@PathVariable String datasetId, HttpServletRequest request) { int resumableChunkNumber = getResumableChunkNumber(request); ResumableInfo info = getResumableInfo(request); try { InputStream is = request.getInputStream(); ByteArrayOutputStream os = new ByteArrayOutputStream(); long readed = 0; long content_length = request.getContentLength(); byte[] bytes = new byte[1024 * 100]; while (readed < content_length) { int r = is.read(bytes); if (r < 0) { break; } os.write(bytes, 0, r); readed += r; } JSONObject resumableObject = new JSONObject(); resumableObject.put("resumableChunkSize", info.resumableChunkSize); resumableObject.put("resumableTotalSize", info.resumableTotalSize); resumableObject.put("resumableIdentifier", info.resumableIdentifier); resumableObject.put("resumableFilename", info.resumableFilename); resumableObject.put("resumableRelativePath", info.resumableRelativePath); String resumableChunk = Base64.encodeBase64String(os.toByteArray()); log.debug(resumableChunk); resumableObject.put("resumableChunk", resumableChunk); String url = properties.sendUploadChunk(datasetId, resumableChunkNumber); log.debug("URL: {}", url); HttpEntity<String> httpEntity = createHttpEntityWithBody(resumableObject.toString()); restTemplate.setErrorHandler(new MyResponseErrorHandler()); ResponseEntity responseEntity = restTemplate.exchange(url, HttpMethod.POST, httpEntity, String.class); String body = responseEntity.getBody().toString(); if (RestUtil.isError(responseEntity.getStatusCode())) { MyErrorResource error = objectMapper.readValue(body, MyErrorResource.class); ExceptionState exceptionState = ExceptionState.parseExceptionState(error.getError()); return getStringResponseEntity(exceptionState); } else if (body.equals("All finished.")) { log.info("Data resource uploaded."); } return ResponseEntity.ok(body); } catch (Exception e) { log.error("Error sending upload chunk: {}", e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error sending upload chunk"); } } private ResponseEntity<String> getStringResponseEntity(ExceptionState exceptionState) { switch (exceptionState) { case DATA_NOT_FOUND_EXCEPTION: log.warn("Dataset not found for uploading resource."); return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Dataset not found for uploading resource."); case DATA_RESOURCE_ALREADY_EXISTS_EXCEPTION: log.warn("Data resource already exist."); return ResponseEntity.status(HttpStatus.CONFLICT).body("Data resource already exist"); case FORBIDDEN_EXCEPTION: log.warn("Uploading of dataset resource forbidden."); return ResponseEntity.status(HttpStatus.FORBIDDEN).body("Uploading of dataset resource forbidden."); case UPLOAD_ALREADY_EXISTS_EXCEPTION: log.warn("Upload of data resource already exist."); return ResponseEntity.status(HttpStatus.CONFLICT).body("Upload of data resource already exist."); default: log.warn("Unknown exception while uploading resource."); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body("Unknown exception while uploading resource."); } } /** * References: * [1] http://stackoverflow.com/questions/25854077/calling-a-servlet-from-another-servlet-after-the-request-dispatcher-forward-meth * [2] http://stackoverflow.com/questions/29712554/how-to-download-a-file-using-a-java-rest-service-and-a-data-stream * [3] http://stackoverflow.com/questions/32988370/download-large-file-from-server-using-rest-template-java-spring-mvc */ @RequestMapping(value = "{datasetId}/resources/{resourceId}", method = RequestMethod.GET) public void getResource(@PathVariable String datasetId, @PathVariable String resourceId, HttpSession session, final HttpServletResponse httpResponse) throws UnsupportedEncodingException, WebServiceRuntimeException { Dataset dataset = invokeAndExtractDataInfo(Long.valueOf(datasetId)); if (!dataset.isDownloadable(session.getAttribute("id").toString())) { throw new WebServiceRuntimeException("Resource download denied!"); } try { // Optional Accept header RequestCallback requestCallback = request -> { request.getHeaders().setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL)); request.getHeaders().set("Authorization", httpScopedSession.getAttribute(webProperties.getSessionJwtToken()).toString()); }; // Streams the response instead of loading it all in memory ResponseExtractor<Void> responseExtractor = getResponseExtractor(httpResponse); restTemplate.execute(properties.downloadResource(datasetId, resourceId), HttpMethod.GET, requestCallback, responseExtractor); } catch (Exception e) { log.error("Error transferring download: {}", e.getMessage()); } } @RequestMapping(value = "{datasetId}/resources/{resourceId}/delete", method = RequestMethod.GET) public String removeResource(@PathVariable String datasetId, @PathVariable String resourceId, RedirectAttributes redirectAttributes) throws WebServiceRuntimeException { HttpEntity<String> request = createHttpEntityHeaderOnly(); restTemplate.setErrorHandler(new MyResponseErrorHandler()); ResponseEntity response = restTemplate.exchange(properties.getResource(datasetId, resourceId), HttpMethod.DELETE, request, String.class); String dataResponseBody = response.getBody().toString(); try { if (RestUtil.isError(response.getStatusCode())) { MyErrorResource error = objectMapper.readValue(dataResponseBody, MyErrorResource.class); ExceptionState exceptionState = ExceptionState.parseExceptionState(error.getError()); if (exceptionState == FORBIDDEN_EXCEPTION) { log.warn("Removing of data resource forbidden."); redirectAttributes.addFlashAttribute(MESSAGE_ATTRIBUTE, error.getMessage()); } else if (exceptionState == DATA_NOT_FOUND_EXCEPTION) { log.warn("Dataset not found for removing resource."); redirectAttributes.addFlashAttribute(MESSAGE_ATTRIBUTE, error.getMessage()); } else if (exceptionState == DATA_RESOURCE_NOT_FOUND_EXCEPTION) { log.warn("Data resource not found for removing."); redirectAttributes.addFlashAttribute(MESSAGE_ATTRIBUTE, error.getMessage()); } else if (exceptionState == DATA_RESOURCE_DELETE_EXCEPTION) { log.warn("Error when removing data resource file."); redirectAttributes.addFlashAttribute(MESSAGE_ATTRIBUTE, error.getMessage()); } else { log.warn("Unknown error for removing data resource."); } } else { log.info("Data resource removed: {}", dataResponseBody); } } catch (IOException e) { log.error("removeResource: {}", e); throw new WebServiceRuntimeException(e.getMessage()); } return "redirect:/data/" + datasetId + "/" + RESOURCES; } private void setContributor(Dataset dataset, HttpSession session) { if (dataset.getContributorId() == null) { dataset.setContributorId(session.getAttribute("id").toString()); } dataset.setContributor(invokeAndExtractUserInfo(dataset.getContributorId())); } private ResponseEntity getResponseEntity(@PathVariable Optional<String> id, HttpEntity<String> request) { ResponseEntity response; if (!id.isPresent()) { response = restTemplate.exchange(properties.getSioDataUrl(), HttpMethod.POST, request, String.class); } else { response = restTemplate.exchange(properties.getDataset(id.get()), HttpMethod.PUT, request, String.class); } return response; } private int getResumableChunkNumber(HttpServletRequest request) { return HttpUtils.toInt(request.getParameter("resumableChunkNumber"), -1); } private ResumableInfo getResumableInfo(HttpServletRequest request) { ResumableInfo info = new ResumableInfo(); info.resumableChunkSize = HttpUtils.toInt(request.getParameter("resumableChunkSize"), -1); info.resumableTotalSize = HttpUtils.toLong(request.getParameter("resumableTotalSize"), -1); info.resumableIdentifier = request.getParameter("resumableIdentifier"); info.resumableFilename = request.getParameter("resumableFilename"); info.resumableRelativePath = request.getParameter("resumableRelativePath"); return info; } private DataAccessRequest extractDataAccessRequest(String json) { log.debug(json); DataAccessRequest dataAccessRequest = new DataAccessRequest(); JSONObject object = new JSONObject(json); dataAccessRequest.setId(object.getLong("id")); dataAccessRequest.setDataId(object.getLong(DATA_ID)); dataAccessRequest.setRequesterId(object.getString("requesterId")); dataAccessRequest.setReason(object.getString("reason")); try { dataAccessRequest.setRequestDate(getZonedDateTime(object.get("requestDate").toString())); } catch (IOException e) { log.warn("Error getting request date {}", e); dataAccessRequest.setRequestDate(null); } try { dataAccessRequest.setApprovedDate(getZonedDateTime(object.get("approvedDate").toString())); } catch (IOException e) { log.warn("Error getting approved date {}", e); dataAccessRequest.setApprovedDate(null); } dataAccessRequest.setRequester(invokeAndExtractUserInfo(dataAccessRequest.getRequesterId())); dataAccessRequest.setDataset(invokeAndExtractDataInfo(dataAccessRequest.getDataId())); return dataAccessRequest; } }