Loading pom.xml +3 −0 Original line number Diff line number Diff line Loading @@ -176,6 +176,9 @@ <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.2</version> <configuration> <trimStackTrace>false</trimStackTrace> </configuration> </plugin> <plugin> <groupId>org.jacoco</groupId> Loading src/main/java/it/inaf/oats/vospace/FileServiceClient.java 0 → 100644 +109 −0 Original line number Diff line number Diff line /* * This file is part of vospace-rest * Copyright (C) 2021 Istituto Nazionale di Astrofisica * SPDX-License-Identifier: GPL-3.0-or-later */ package it.inaf.oats.vospace; import com.fasterxml.jackson.databind.ObjectMapper; import it.inaf.ia2.aa.data.User; import java.io.OutputStream; import java.util.List; import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; import net.ivoa.xml.vospace.v2.Transfer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; @Component public class FileServiceClient { private static final ObjectMapper MAPPER = new ObjectMapper(); @Value("${vospace-authority}") private String authority; @Value("${file-service-url}") private String fileServiceUrl; @Autowired private RestTemplate restTemplate; @Autowired private HttpServletRequest request; public String startArchiveJob(Transfer transfer, String jobId) { List<String> vosPaths = transfer.getTarget().stream() .map(p -> p.substring("vos://".length() + authority.length())).collect(Collectors.toList()); ArchiveRequest archiveRequest = new ArchiveRequest(); archiveRequest.setJobId(jobId); archiveRequest.setPaths(vosPaths); archiveRequest.setType(archiveTypeFromViewUri(transfer.getView().getUri())); String url = fileServiceUrl + "/archive"; String token = ((User) request.getUserPrincipal()).getAccessToken(); return restTemplate.execute(url, HttpMethod.POST, req -> { HttpHeaders headers = req.getHeaders(); if (token != null) { headers.setBearerAuth(token); } headers.setContentType(MediaType.APPLICATION_JSON); try ( OutputStream os = req.getBody()) { MAPPER.writeValue(os, archiveRequest); } }, res -> { return res.getHeaders().getLocation().toString(); }, new Object[]{}); } private static class ArchiveRequest { private String type; private String jobId; private List<String> paths; public String getType() { return type; } public void setType(String type) { this.type = type; } public String getJobId() { return jobId; } public void setJobId(String jobId) { this.jobId = jobId; } public List<String> getPaths() { return paths; } public void setPaths(List<String> paths) { this.paths = paths; } } private static String archiveTypeFromViewUri(String viewUri) { switch (viewUri) { case "ivo://ia2.inaf.it/vospace/views#tar": return "TAR"; case "ivo://ia2.inaf.it/vospace/views#zip": return "ZIP"; default: throw new IllegalArgumentException("Archive type not defined for " + viewUri); } } } src/main/java/it/inaf/oats/vospace/UriService.java +19 −1 Original line number Diff line number Diff line Loading @@ -12,6 +12,7 @@ import it.inaf.oats.vospace.JobService.JobDirection; import it.inaf.oats.vospace.datamodel.NodeProperties; import it.inaf.oats.vospace.datamodel.NodeUtils; import static it.inaf.oats.vospace.datamodel.NodeUtils.urlEncodePath; import it.inaf.oats.vospace.datamodel.Views; import it.inaf.oats.vospace.exception.InternalFaultException; import it.inaf.oats.vospace.exception.InvalidArgumentException; import it.inaf.oats.vospace.exception.NodeNotFoundException; Loading Loading @@ -63,6 +64,9 @@ public class UriService { @Autowired private CreateNodeService createNodeService; @Autowired private FileServiceClient fileServiceClient; public void setTransferJobResult(JobSummary job, Transfer transfer) { List<ResultReference> results = new ArrayList<>(); Loading Loading @@ -143,8 +147,9 @@ public class UriService { } private String getEndpoint(JobSummary job, Transfer transfer) { boolean isArchiveView = isArchiveView(transfer); if (transfer.getTarget().size() != 1) { if (!isArchiveView && transfer.getTarget().size() != 1) { throw new InvalidArgumentException("Invalid target size: " + transfer.getTarget().size()); } Loading Loading @@ -181,6 +186,10 @@ public class UriService { throw new NodeBusyException(relativePath); } if (isArchiveView) { return fileServiceClient.startArchiveJob(transfer, job.getJobId()); } Location location = locationDAO.getNodeLocation(relativePath).orElse(null); String endpoint; Loading @@ -205,6 +214,15 @@ public class UriService { return endpoint; } private boolean isArchiveView(Transfer transfer) { if (transfer.getView() == null) { return false; } String viewUri = transfer.getView().getUri(); return Views.TAR_VIEW_URI.equals(viewUri) || Views.ZIP_VIEW_URI.equals(viewUri); } private String getEndpointToken(String endpoint) { String token = ((User) servletRequest.getUserPrincipal()).getAccessToken(); Loading src/main/java/it/inaf/oats/vospace/VospaceApplication.java +6 −0 Original line number Diff line number Diff line Loading @@ -12,6 +12,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import it.inaf.ia2.aa.TokenFilter; import org.springframework.web.client.RestTemplate; @SpringBootApplication public class VospaceApplication { Loading @@ -32,4 +33,9 @@ public class VospaceApplication { public ServletRapClient servletRapClient() { return (ServletRapClient) ServiceLocator.getInstance().getRapClient(); } @Bean public RestTemplate restTemplate() { return new RestTemplate(); } } src/test/java/it/inaf/oats/vospace/FileServiceClientTest.java 0 → 100644 +113 −0 Original line number Diff line number Diff line /* * This file is part of vospace-rest * Copyright (C) 2021 Istituto Nazionale di Astrofisica * SPDX-License-Identifier: GPL-3.0-or-later */ package it.inaf.oats.vospace; import it.inaf.ia2.aa.data.User; import it.inaf.oats.vospace.datamodel.Views; import java.io.ByteArrayOutputStream; import java.net.URI; import java.util.Arrays; import javax.servlet.http.HttpServletRequest; import net.ivoa.xml.vospace.v2.Transfer; import net.ivoa.xml.vospace.v2.View; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import org.mockito.InjectMocks; import org.mockito.Mock; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.ClientHttpResponse; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.web.client.RequestCallback; import org.springframework.web.client.ResponseExtractor; import org.springframework.web.client.RestTemplate; @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) public class FileServiceClientTest { @Mock private RestTemplate restTemplate; @Mock private HttpServletRequest request; @InjectMocks private FileServiceClient fileServiceClient; @BeforeEach public void setUp() { ReflectionTestUtils.setField(fileServiceClient, "authority", "example.com!vospace"); ReflectionTestUtils.setField(fileServiceClient, "fileServiceUrl", "http://file-service"); } @Test public void testTarArchiveJob() { testStartArchiveJob(Views.TAR_VIEW_URI); } @Test public void testZipArchiveJob() { testStartArchiveJob(Views.ZIP_VIEW_URI); } @Test public void testInvalidArchiveJob() { try { testStartArchiveJob("foo"); fail("IllegalArgumentException was expected"); } catch (IllegalArgumentException ex) { } } private void testStartArchiveJob(String viewUri) { Transfer transfer = new Transfer(); transfer.setDirection("pullFromVoSpace"); transfer.setTarget(Arrays.asList("vos://example.com!vospace/file1", "vos://example.com!vospace/file2")); View view = new View(); view.setUri(viewUri); transfer.setView(view); User user = mock(User.class); when(user.getAccessToken()).thenReturn("<token>"); when(request.getUserPrincipal()).thenReturn(user); doAnswer(invocation -> { RequestCallback requestCallback = invocation.getArgument(2); ClientHttpRequest mockedRequest = mock(ClientHttpRequest.class); HttpHeaders mockedRequestHeaders = mock(HttpHeaders.class); when(mockedRequest.getBody()).thenReturn(new ByteArrayOutputStream()); when(mockedRequest.getHeaders()).thenReturn(mockedRequestHeaders); requestCallback.doWithRequest(mockedRequest); ResponseExtractor responseExtractor = invocation.getArgument(3); ClientHttpResponse mockedResponse = mock(ClientHttpResponse.class); HttpHeaders mockedResponseHeaders = mock(HttpHeaders.class); when(mockedResponseHeaders.getLocation()).thenReturn(new URI("http://file-service/archive/result")); when(mockedResponse.getHeaders()).thenReturn(mockedResponseHeaders); responseExtractor.extractData(mockedResponse); return mockedResponseHeaders.getLocation().toString(); }).when(restTemplate).execute(eq("http://file-service/archive"), eq(HttpMethod.POST), any(RequestCallback.class), any(ResponseExtractor.class), any(Object[].class)); String redirect = fileServiceClient.startArchiveJob(transfer, "job123"); assertEquals("http://file-service/archive/result", redirect); } } Loading
pom.xml +3 −0 Original line number Diff line number Diff line Loading @@ -176,6 +176,9 @@ <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.2</version> <configuration> <trimStackTrace>false</trimStackTrace> </configuration> </plugin> <plugin> <groupId>org.jacoco</groupId> Loading
src/main/java/it/inaf/oats/vospace/FileServiceClient.java 0 → 100644 +109 −0 Original line number Diff line number Diff line /* * This file is part of vospace-rest * Copyright (C) 2021 Istituto Nazionale di Astrofisica * SPDX-License-Identifier: GPL-3.0-or-later */ package it.inaf.oats.vospace; import com.fasterxml.jackson.databind.ObjectMapper; import it.inaf.ia2.aa.data.User; import java.io.OutputStream; import java.util.List; import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; import net.ivoa.xml.vospace.v2.Transfer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; @Component public class FileServiceClient { private static final ObjectMapper MAPPER = new ObjectMapper(); @Value("${vospace-authority}") private String authority; @Value("${file-service-url}") private String fileServiceUrl; @Autowired private RestTemplate restTemplate; @Autowired private HttpServletRequest request; public String startArchiveJob(Transfer transfer, String jobId) { List<String> vosPaths = transfer.getTarget().stream() .map(p -> p.substring("vos://".length() + authority.length())).collect(Collectors.toList()); ArchiveRequest archiveRequest = new ArchiveRequest(); archiveRequest.setJobId(jobId); archiveRequest.setPaths(vosPaths); archiveRequest.setType(archiveTypeFromViewUri(transfer.getView().getUri())); String url = fileServiceUrl + "/archive"; String token = ((User) request.getUserPrincipal()).getAccessToken(); return restTemplate.execute(url, HttpMethod.POST, req -> { HttpHeaders headers = req.getHeaders(); if (token != null) { headers.setBearerAuth(token); } headers.setContentType(MediaType.APPLICATION_JSON); try ( OutputStream os = req.getBody()) { MAPPER.writeValue(os, archiveRequest); } }, res -> { return res.getHeaders().getLocation().toString(); }, new Object[]{}); } private static class ArchiveRequest { private String type; private String jobId; private List<String> paths; public String getType() { return type; } public void setType(String type) { this.type = type; } public String getJobId() { return jobId; } public void setJobId(String jobId) { this.jobId = jobId; } public List<String> getPaths() { return paths; } public void setPaths(List<String> paths) { this.paths = paths; } } private static String archiveTypeFromViewUri(String viewUri) { switch (viewUri) { case "ivo://ia2.inaf.it/vospace/views#tar": return "TAR"; case "ivo://ia2.inaf.it/vospace/views#zip": return "ZIP"; default: throw new IllegalArgumentException("Archive type not defined for " + viewUri); } } }
src/main/java/it/inaf/oats/vospace/UriService.java +19 −1 Original line number Diff line number Diff line Loading @@ -12,6 +12,7 @@ import it.inaf.oats.vospace.JobService.JobDirection; import it.inaf.oats.vospace.datamodel.NodeProperties; import it.inaf.oats.vospace.datamodel.NodeUtils; import static it.inaf.oats.vospace.datamodel.NodeUtils.urlEncodePath; import it.inaf.oats.vospace.datamodel.Views; import it.inaf.oats.vospace.exception.InternalFaultException; import it.inaf.oats.vospace.exception.InvalidArgumentException; import it.inaf.oats.vospace.exception.NodeNotFoundException; Loading Loading @@ -63,6 +64,9 @@ public class UriService { @Autowired private CreateNodeService createNodeService; @Autowired private FileServiceClient fileServiceClient; public void setTransferJobResult(JobSummary job, Transfer transfer) { List<ResultReference> results = new ArrayList<>(); Loading Loading @@ -143,8 +147,9 @@ public class UriService { } private String getEndpoint(JobSummary job, Transfer transfer) { boolean isArchiveView = isArchiveView(transfer); if (transfer.getTarget().size() != 1) { if (!isArchiveView && transfer.getTarget().size() != 1) { throw new InvalidArgumentException("Invalid target size: " + transfer.getTarget().size()); } Loading Loading @@ -181,6 +186,10 @@ public class UriService { throw new NodeBusyException(relativePath); } if (isArchiveView) { return fileServiceClient.startArchiveJob(transfer, job.getJobId()); } Location location = locationDAO.getNodeLocation(relativePath).orElse(null); String endpoint; Loading @@ -205,6 +214,15 @@ public class UriService { return endpoint; } private boolean isArchiveView(Transfer transfer) { if (transfer.getView() == null) { return false; } String viewUri = transfer.getView().getUri(); return Views.TAR_VIEW_URI.equals(viewUri) || Views.ZIP_VIEW_URI.equals(viewUri); } private String getEndpointToken(String endpoint) { String token = ((User) servletRequest.getUserPrincipal()).getAccessToken(); Loading
src/main/java/it/inaf/oats/vospace/VospaceApplication.java +6 −0 Original line number Diff line number Diff line Loading @@ -12,6 +12,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import it.inaf.ia2.aa.TokenFilter; import org.springframework.web.client.RestTemplate; @SpringBootApplication public class VospaceApplication { Loading @@ -32,4 +33,9 @@ public class VospaceApplication { public ServletRapClient servletRapClient() { return (ServletRapClient) ServiceLocator.getInstance().getRapClient(); } @Bean public RestTemplate restTemplate() { return new RestTemplate(); } }
src/test/java/it/inaf/oats/vospace/FileServiceClientTest.java 0 → 100644 +113 −0 Original line number Diff line number Diff line /* * This file is part of vospace-rest * Copyright (C) 2021 Istituto Nazionale di Astrofisica * SPDX-License-Identifier: GPL-3.0-or-later */ package it.inaf.oats.vospace; import it.inaf.ia2.aa.data.User; import it.inaf.oats.vospace.datamodel.Views; import java.io.ByteArrayOutputStream; import java.net.URI; import java.util.Arrays; import javax.servlet.http.HttpServletRequest; import net.ivoa.xml.vospace.v2.Transfer; import net.ivoa.xml.vospace.v2.View; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import org.mockito.InjectMocks; import org.mockito.Mock; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.ClientHttpResponse; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.web.client.RequestCallback; import org.springframework.web.client.ResponseExtractor; import org.springframework.web.client.RestTemplate; @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) public class FileServiceClientTest { @Mock private RestTemplate restTemplate; @Mock private HttpServletRequest request; @InjectMocks private FileServiceClient fileServiceClient; @BeforeEach public void setUp() { ReflectionTestUtils.setField(fileServiceClient, "authority", "example.com!vospace"); ReflectionTestUtils.setField(fileServiceClient, "fileServiceUrl", "http://file-service"); } @Test public void testTarArchiveJob() { testStartArchiveJob(Views.TAR_VIEW_URI); } @Test public void testZipArchiveJob() { testStartArchiveJob(Views.ZIP_VIEW_URI); } @Test public void testInvalidArchiveJob() { try { testStartArchiveJob("foo"); fail("IllegalArgumentException was expected"); } catch (IllegalArgumentException ex) { } } private void testStartArchiveJob(String viewUri) { Transfer transfer = new Transfer(); transfer.setDirection("pullFromVoSpace"); transfer.setTarget(Arrays.asList("vos://example.com!vospace/file1", "vos://example.com!vospace/file2")); View view = new View(); view.setUri(viewUri); transfer.setView(view); User user = mock(User.class); when(user.getAccessToken()).thenReturn("<token>"); when(request.getUserPrincipal()).thenReturn(user); doAnswer(invocation -> { RequestCallback requestCallback = invocation.getArgument(2); ClientHttpRequest mockedRequest = mock(ClientHttpRequest.class); HttpHeaders mockedRequestHeaders = mock(HttpHeaders.class); when(mockedRequest.getBody()).thenReturn(new ByteArrayOutputStream()); when(mockedRequest.getHeaders()).thenReturn(mockedRequestHeaders); requestCallback.doWithRequest(mockedRequest); ResponseExtractor responseExtractor = invocation.getArgument(3); ClientHttpResponse mockedResponse = mock(ClientHttpResponse.class); HttpHeaders mockedResponseHeaders = mock(HttpHeaders.class); when(mockedResponseHeaders.getLocation()).thenReturn(new URI("http://file-service/archive/result")); when(mockedResponse.getHeaders()).thenReturn(mockedResponseHeaders); responseExtractor.extractData(mockedResponse); return mockedResponseHeaders.getLocation().toString(); }).when(restTemplate).execute(eq("http://file-service/archive"), eq(HttpMethod.POST), any(RequestCallback.class), any(ResponseExtractor.class), any(Object[].class)); String redirect = fileServiceClient.startArchiveJob(transfer, "job123"); assertEquals("http://file-service/archive/result", redirect); } }