Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Story/GECO-140: Adding deletion endpoint to the V2 API #114

Open
wants to merge 31 commits into
base: story/GECO-139
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
da57832
[GECO-140] Adding new delete document controller to the v2 API
diya17 Mar 10, 2023
7e07f0e
[GECO-140] Making changes to the controller endpoint response and add…
diya17 Mar 16, 2023
354661a
Merge branch 'story/GECO-139' into story/GECO-140
diya17 May 26, 2023
1dc8fcb
[GECO-140] : Addressing review comments
diya17 May 27, 2023
8fed293
[GECO-140] Changing the status for the deletion endpoint and adding a…
diya17 May 30, 2023
1235a6b
[GECO-140] Adding javadocs and test case changes
diya17 May 30, 2023
5b0ccf3
[GECO-140] Adding and fixing tests
diya17 Jun 1, 2023
6c4d733
Merge branch 'story/GECO-139' into story/GECO-140
diya17 Jun 15, 2023
6c74ab9
[GECO-140] Removing generateUnauthorizedUserResponse from userHelper
diya17 Jun 15, 2023
f9e9772
[GECO-140] Refactoring code and and testcases
diya17 Jun 23, 2023
617a099
Merge branch 'story/GECO-139' into story/GECO-140
diya17 Jun 26, 2023
e515ac6
[GECO-140] Making changes to check user access using citesphere
diya17 Jun 30, 2023
b1013f7
Merge branch 'story/GECO-139' into story/GECO-140
diya17 Jun 30, 2023
93f7d13
[GECO-140] Final changes after using citesphere connector
diya17 Jul 3, 2023
753ad10
[GECO-140] Changing Giles Deletion API and also saving Processing req…
diya17 Jul 25, 2023
5a472cd
[GECO-140] Changes to API and processing requests
diya17 Jul 28, 2023
bc32897
[GECO-140] Adding storage deletion request to orm.xml
diya17 Aug 1, 2023
0379d09
[GECO-140] Setting request status for storage deletion in processing …
diya17 Aug 1, 2023
ebcdfd1
[GECO-140] Deleting requests for document initial commit
diya17 Aug 2, 2023
1001a55
[GECO-140] Deleting requests
diya17 Aug 4, 2023
8b50bf9
[GECO-140] Modifying request client to use spring data and modifying …
diya17 Aug 7, 2023
4ea57f2
Merge branch 'story/GECO-139' into story/GECO-140
diya17 Sep 22, 2023
984f9f5
Fix path to jsp
jdamerow Mar 29, 2024
da5e90c
undo change
jdamerow Mar 29, 2024
5403214
Merge branch 'story/GECO-139' into story/GECO-140
diya17 Apr 10, 2024
5fc31a9
Merge branch 'story/GECO-139' into story/GECO-140
diya17 Apr 10, 2024
0a37341
Merge branch 'develop' into story/GECO-140
diya17 Apr 10, 2024
bb4d6a2
Update delete document service
diya17 Apr 12, 2024
8e2d05e
[GECO-140] Update kafka
diya17 Apr 12, 2024
78f7f38
[GECO-140] : Update deletion completion
diya17 Apr 12, 2024
9736973
[GECO-140] Removing unused files
diya17 Apr 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
import edu.asu.diging.gilesecosystem.septemberutil.service.ISystemMessageHandler;
import edu.asu.diging.gilesecosystem.web.api.util.IResponseHelper;

/**
A helper class that generates HTTP responses with JSON content.
*/
@Component
public class ResponseHelper implements IResponseHelper {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package edu.asu.diging.gilesecosystem.web.api.v2;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import edu.asu.diging.gilesecosystem.requests.RequestStatus;
import edu.asu.diging.gilesecosystem.util.properties.IPropertiesManager;
import edu.asu.diging.gilesecosystem.web.api.util.IResponseHelper;
import edu.asu.diging.gilesecosystem.web.config.CitesphereToken;
import edu.asu.diging.gilesecosystem.web.config.IUserHelper;
import edu.asu.diging.gilesecosystem.web.core.citesphere.ICitesphereConnector;
import edu.asu.diging.gilesecosystem.web.core.model.IDocument;
import edu.asu.diging.gilesecosystem.web.core.model.IProcessingRequest;
import edu.asu.diging.gilesecosystem.web.core.service.core.ITransactionalDocumentService;
import edu.asu.diging.gilesecosystem.web.core.service.core.ITransactionalProcessingRequestService;
import edu.asu.diging.gilesecosystem.web.core.service.delete.IDeleteDocumentService;
import edu.asu.diging.gilesecosystem.web.core.service.properties.Properties;
import edu.asu.diging.gilesecosystem.web.core.users.CitesphereUser;

@Controller
public class V2DeleteDocumentController {

@Autowired
private IDeleteDocumentService deleteDocumentService;

@Autowired
private ITransactionalDocumentService documentService;

@Autowired
private IResponseHelper responseHelper;

@Autowired
private IPropertiesManager propertyManager;

@Autowired
private IUserHelper userHelper;

@Autowired
private ICitesphereConnector citesphereConnector;

@Autowired
private ITransactionalProcessingRequestService processingRequestService;

@Value("${giles_check_deletion_endpoint_v2}")
private String checkDeleteEndpoint;

@RequestMapping(value = "/api/v2/resources/documents/{documentId}", method = RequestMethod.DELETE, produces = "application/json;charset=UTF-8")
public ResponseEntity<String> deleteDocument(@PathVariable("documentId") String documentId, CitesphereToken citesphereToken) {
IDocument document = documentService.getDocument(documentId);
Map<String, String> msgs = new HashMap<String, String>();
if (document == null) {
msgs.put("errorMsg", "Document with id: " + documentId + " does not exist.");
msgs.put("errorCode", "404");
return responseHelper.generateResponse(msgs, HttpStatus.NOT_FOUND);
}
if (!citesphereConnector.hasAccess(document.getId(), ((CitesphereUser)citesphereToken.getPrincipal()).getUsername())) {
Map<String, String> unauthorizedMsgs = new HashMap<String, String>();
unauthorizedMsgs.put("errorMsg", "User is not authorized to delete the document with id " + document.getId());
unauthorizedMsgs.put("errorCode", "403");
return responseHelper.generateResponse(unauthorizedMsgs, HttpStatus.FORBIDDEN);
}
deleteDocumentService.deleteDocument(document);
msgs.put("checkUrl", propertyManager.getProperty(Properties.GILES_URL) + checkDeleteEndpoint + documentId);

return responseHelper.generateResponse(msgs, HttpStatus.OK);
}

@RequestMapping(value = "/api/v2/files/deletion/check/{documentId}", method = RequestMethod.GET)
public ResponseEntity<String> checkDocumentDeletion(HttpServletRequest request,
@PathVariable String documentId, CitesphereToken citesphereToken) {
IDocument document = documentService.getDocument(documentId);
if (document == null) {
Map<String, String> successMsgs = new HashMap<String, String>();
successMsgs.put("successMessage", "Document Id: " + documentId + " is deleted.");
return responseHelper.generateResponse(successMsgs, HttpStatus.OK);
}
if (!citesphereConnector.hasAccess(document.getId(), ((CitesphereUser)citesphereToken.getPrincipal()).getUsername())) {
Map<String, String> unauthorizedMsgs = new HashMap<String, String>();
unauthorizedMsgs.put("errorMsg", "User is not authorized to check status for document id " + document.getId());
unauthorizedMsgs.put("errorCode", "403");
return responseHelper.generateResponse(unauthorizedMsgs, HttpStatus.FORBIDDEN);
}
if (!document.getRequestId().isEmpty()) {
List<IProcessingRequest> pRequests = processingRequestService.getProcRequestsByRequestId(document.getRequestId());
for (IProcessingRequest pReq : pRequests) {
if (pReq.getRequestStatus().equals(RequestStatus.FAILED)) {
Map<String, String> deletionErrorMsg = new HashMap<String, String>();
deletionErrorMsg.put("progressInfo", "There was an error in deleting the document. Please try again.");
return responseHelper.generateResponse(deletionErrorMsg, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
Map<String, String> msgs = new HashMap<String, String>();
msgs.put("progressInfo", "Deletion in progress. Please check back later.");
return responseHelper.generateResponse(msgs, HttpStatus.ACCEPTED);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package edu.asu.diging.gilesecosystem.web.core.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import edu.asu.diging.gilesecosystem.requests.impl.Request;

@Repository
public interface RequestRepository extends JpaRepository<Request, String> {
void deleteByDocumentId(String documentId);
}
3 changes: 3 additions & 0 deletions giles-eco/src/main/resources/META-INF/hibernate/orm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@
<key column="id" />
<property name="filename" type="string" />
</joined-subclass>
<joined-subclass name="edu.asu.diging.gilesecosystem.requests.impl.StorageDeletionRequest" table="StorageDeletionRequest">
<key column="id" />
</joined-subclass>
</class>
<class name="edu.asu.diging.gilesecosystem.requests.impl.Page" table="RequestPage" >
<id name="id" column="pageId">
Expand Down
2 changes: 2 additions & 0 deletions giles-eco/src/main/resources/META-INF/orm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
</embeddable>
<embeddable class="edu.asu.diging.gilesecosystem.requests.impl.TextExtractionRequest" access="FIELD">
</embeddable>
<embeddable class="edu.asu.diging.gilesecosystem.requests.impl.StorageDeletionRequest" access="FIELD">
</embeddable>

<embeddable class="edu.asu.diging.gilesecosystem.requests.impl.Page" access="FIELD">
</embeddable>
Expand Down
1 change: 1 addition & 0 deletions giles-eco/src/main/resources/config.properties
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ giles_digilib_endpoint=/rest/digilib
giles_file_endpoint=/rest/files/
giles_check_upload_endpoint=/rest/files/upload/check/
giles_check_upload_endpoint_v2=/api/v2/files/upload/check/
giles_check_deletion_endpoint_v2=/api/v2/files/deletion/check/
giles_file_content_suffix=/content

# file uploads
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
package edu.asu.diging.gilesecosystem.web.api.v2;

import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.node.ObjectNode;

import edu.asu.diging.gilesecosystem.requests.RequestStatus;
import edu.asu.diging.gilesecosystem.util.properties.IPropertiesManager;
import edu.asu.diging.gilesecosystem.web.api.util.IResponseHelper;
import edu.asu.diging.gilesecosystem.web.config.CitesphereToken;
import edu.asu.diging.gilesecosystem.web.core.citesphere.ICitesphereConnector;
import edu.asu.diging.gilesecosystem.web.core.model.DocumentAccess;
import edu.asu.diging.gilesecosystem.web.core.model.IDocument;
import edu.asu.diging.gilesecosystem.web.core.model.IProcessingRequest;
import edu.asu.diging.gilesecosystem.web.core.model.impl.Document;
import edu.asu.diging.gilesecosystem.web.core.model.impl.ProcessingRequest;
import edu.asu.diging.gilesecosystem.web.core.service.core.ITransactionalDocumentService;
import edu.asu.diging.gilesecosystem.web.core.service.core.ITransactionalProcessingRequestService;
import edu.asu.diging.gilesecosystem.web.core.service.delete.IDeleteDocumentService;
import edu.asu.diging.gilesecosystem.web.core.service.properties.Properties;
import edu.asu.diging.gilesecosystem.web.core.users.CitesphereUser;

public class V2DeleteDocumentControllerTest {

@Mock
private IDeleteDocumentService deleteDocumentService;

@Mock
private ITransactionalDocumentService documentService;

@Mock
private IPropertiesManager propertyManager;

@Mock
private IResponseHelper responseHelper;

@Mock
private ICitesphereConnector citesphereConnector;

@Mock
private ITransactionalProcessingRequestService processingRequestService;

@InjectMocks
private V2DeleteDocumentController v2DeleteDocumentController;

private String DOCUMENT_ID = "documentId";
private DocumentAccess access = DocumentAccess.PRIVATE;
private String FILE_ID = "fileId";
private String UPLOAD_ID = "uploadId";
private CitesphereToken CITESPHERE_TOKEN = new CitesphereToken("71b9cc36-939d-4e28-89e9-e5bfca1b26c3");
IDocument document;

@Before
public void setUp() {
document = createDocument();
v2DeleteDocumentController = new V2DeleteDocumentController();
MockitoAnnotations.initMocks(this);
Mockito.when(propertyManager.getProperty(Properties.GILES_URL)).thenReturn("http://localhost:8085/giles/");
Mockito.when(citesphereConnector.hasAccess(Mockito.anyString(), Mockito.anyString())).thenReturn(true);
CITESPHERE_TOKEN.setPrincipal(new CitesphereUser("testUser", "oauth", new ArrayList<>()));
}

@Test
public void test_deleteDocument_success() {
Mockito.when(documentService.getDocument(DOCUMENT_ID)).thenReturn(document);
Map<String, String> msgs = new HashMap<String, String>();
msgs.put("checkUrl", "http://localhost:8085/giles/api/v2/files/deletion/check/" + DOCUMENT_ID);
Mockito.when(responseHelper.generateResponse(Mockito.anyMap(), Mockito.any(HttpStatus.class))).thenReturn(generateResponse(msgs, HttpStatus.OK));
ResponseEntity<String> response = v2DeleteDocumentController.deleteDocument(DOCUMENT_ID, CITESPHERE_TOKEN);
Assert.assertEquals(HttpStatus.OK, response.getStatusCode());
Assert.assertTrue(response.getBody().contains("http://localhost:8085/giles/api/v2/files/deletion/check/documentId"));
}

@Test
public void test_deleteDocument_whenUserIsNotAuthorized() {
Mockito.when(documentService.getDocument(DOCUMENT_ID)).thenReturn(document);
Mockito.when(citesphereConnector.hasAccess(Mockito.anyString(), Mockito.anyString())).thenReturn(false);
Map<String, String> unauthorizedMsgs = new HashMap<String, String>();
unauthorizedMsgs.put("errorMsg", "User is not authorized to delete the document with id " + document.getId());
unauthorizedMsgs.put("errorCode", "401");
Mockito.when(responseHelper.generateResponse(Mockito.anyMap(), Mockito.any(HttpStatus.class))).thenReturn(generateResponse(unauthorizedMsgs, HttpStatus.FORBIDDEN));
ResponseEntity<String> response = v2DeleteDocumentController.deleteDocument(DOCUMENT_ID, CITESPHERE_TOKEN);
Assert.assertEquals(HttpStatus.FORBIDDEN, response.getStatusCode());
}

@Test
public void test_deleteDocument_notFound() {
Mockito.when(documentService.getDocument(DOCUMENT_ID)).thenReturn(null);
Map<String, String> msgs = new HashMap<String, String>();
msgs.put("errorMsg", "Document with id: " + DOCUMENT_ID + " does not exist.");
msgs.put("errorCode", "404");
Mockito.when(responseHelper.generateResponse(Mockito.anyMap(), Mockito.any(HttpStatus.class))).thenReturn(generateResponse(msgs, HttpStatus.NOT_FOUND));
ResponseEntity<String> response = v2DeleteDocumentController.deleteDocument(DOCUMENT_ID, CITESPHERE_TOKEN);
Assert.assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode());
}

@Test
public void test_checkDocumentDeletion_whenDocumentIsDeleted() {
Mockito.when(documentService.getDocument(DOCUMENT_ID)).thenReturn(null);
Map<String, String> successMsgs = new HashMap<String, String>();
successMsgs.put("successMessage", "Document Id: " + DOCUMENT_ID + " is deleted.");
Mockito.when(responseHelper.generateResponse(Mockito.anyMap(), Mockito.any(HttpStatus.class))).thenReturn(generateResponse(successMsgs, HttpStatus.OK));
ResponseEntity<String> response = v2DeleteDocumentController.checkDocumentDeletion(null, DOCUMENT_ID, CITESPHERE_TOKEN);
Assert.assertEquals(HttpStatus.OK, response.getStatusCode());
}

@Test
public void test_checkDocumentDeletion_whenDocumentIsNotDeleted() {
Mockito.when(documentService.getDocument(DOCUMENT_ID)).thenReturn(document);
Map<String, String> msgs = new HashMap<String, String>();
msgs.put("progressInfo", "Deletion in progress. Please check back later.");
Mockito.when(responseHelper.generateResponse(Mockito.anyMap(), Mockito.any(HttpStatus.class))).thenReturn(generateResponse(msgs, HttpStatus.OK));
ResponseEntity<String> response = v2DeleteDocumentController.checkDocumentDeletion(null, DOCUMENT_ID, CITESPHERE_TOKEN);
Assert.assertEquals(HttpStatus.OK, response.getStatusCode());
}

@Test
public void test_checkDocumentDeletion_whenUserDoesNotHavePermissionToCheckDocumentStatus() {
Mockito.when(documentService.getDocument(DOCUMENT_ID)).thenReturn(document);
Mockito.when(citesphereConnector.hasAccess(Mockito.anyString(), Mockito.anyString())).thenReturn(false);
Map<String, String> unauthorizedMsgs = new HashMap<String, String>();
unauthorizedMsgs.put("errorMsg", "User is not authorized to check status for document id " + document.getId());
unauthorizedMsgs.put("errorCode", "401");
Mockito.when(responseHelper.generateResponse(Mockito.anyMap(), Mockito.any(HttpStatus.class))).thenReturn(generateResponse(unauthorizedMsgs, HttpStatus.FORBIDDEN));
ResponseEntity<String> response = v2DeleteDocumentController.deleteDocument(DOCUMENT_ID, CITESPHERE_TOKEN);
Assert.assertEquals(HttpStatus.FORBIDDEN, response.getStatusCode());
}

@Test
public void test_checkDocumentDeletion_whenDeletionFailed() {
Mockito.when(documentService.getDocument(DOCUMENT_ID)).thenReturn(document);
Map<String, String> msgs = new HashMap<String, String>();
msgs.put("progressInfo", "Deletion in progress. Please check back later.");
Mockito.when(responseHelper.generateResponse(Mockito.anyMap(), Mockito.any(HttpStatus.class))).thenReturn(generateResponse(msgs, HttpStatus.INTERNAL_SERVER_ERROR));
IProcessingRequest procRequest = new ProcessingRequest();
procRequest.setRequestStatus(RequestStatus.FAILED);
List<IProcessingRequest> procList = new ArrayList<IProcessingRequest>();
procList.add(procRequest);
Mockito.when(processingRequestService.getProcRequestsByRequestId(Mockito.anyString())).thenReturn(procList);
ResponseEntity<String> response = v2DeleteDocumentController.deleteDocument(DOCUMENT_ID, CITESPHERE_TOKEN);
Assert.assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());
}

private Document createDocument() {
Document document = new Document();
document.setId(DOCUMENT_ID);
document.setAccess(access);
document.setFileIds(Arrays.asList(FILE_ID));
document.setUploadId(UPLOAD_ID);
document.setUploadedFileId(FILE_ID);
document.setRequestId("REQ123");
return document;
}

private ResponseEntity<String> generateResponse(Map<String, String> msgs,
HttpStatus status) {
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
ObjectNode root = mapper.createObjectNode();
for (String key : msgs.keySet()) {
root.put(key, msgs.get(key));
}

StringWriter sw = new StringWriter();
try {
mapper.writeValue(sw, root);
} catch (IOException e) {
return new ResponseEntity<String>(
"{\"errorMsg\": \"Could not write json result.\", \"errorCode\": \"errorCode\": \"500\" }",
HttpStatus.INTERNAL_SERVER_ERROR);
}

return new ResponseEntity<String>(sw.toString(), status);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import edu.asu.diging.gilesecosystem.web.core.service.core.ITransactionalDocumentService;
import edu.asu.diging.gilesecosystem.web.core.service.core.ITransactionalFileService;
import edu.asu.diging.gilesecosystem.web.core.service.core.ITransactionalProcessingRequestService;
import edu.asu.diging.gilesecosystem.web.core.service.core.ITransactionalRequestService;
import edu.asu.diging.gilesecosystem.web.core.service.core.ITransactionalUploadService;
import edu.asu.diging.gilesecosystem.web.core.service.delete.IDeleteDocumentService;
import edu.asu.diging.gilesecosystem.web.core.service.delete.impl.DeleteDocumentService;
Expand Down Expand Up @@ -71,6 +72,9 @@ public class DeleteDocumentServiceTest {
@Mock
private ITransactionalProcessingRequestService processingRequestService;

@Mock
private ITransactionalRequestService requestService;

@InjectMocks
private IDeleteDocumentService factoryToTest;

Expand Down