Commit f9a7d7ec authored by Sonia Zorba's avatar Sonia Zorba
Browse files

Handled errors in transfer (empty protocols list)

parent feba8ee8
Loading
Loading
Loading
Loading
+45 −5
Original line number Diff line number Diff line
@@ -9,6 +9,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import it.inaf.ia2.aa.data.User;
import it.inaf.ia2.vospace.ui.VOSpaceUiApplication;
import it.inaf.ia2.vospace.ui.data.Job;
import it.inaf.ia2.vospace.ui.exception.BadRequestException;
import it.inaf.ia2.vospace.ui.exception.VOSpaceException;
import static it.inaf.oats.vospace.datamodel.NodeUtils.urlEncodePath;
import java.io.IOException;
@@ -21,11 +22,16 @@ import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ForkJoinPool;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
@@ -96,7 +102,7 @@ public class VOSpaceClient {
        return call(request, BodyHandlers.ofInputStream(), 200, res -> unmarshal(res, JobSummary.class));
    }

    public List<Protocol> getFileServiceEndpoints(Transfer transfer) {
    public String getFileServiceEndpoint(Transfer transfer) {

        HttpRequest request = getRequest("/synctrans")
                .header("Accept", useJson ? "application/json" : "text/xml")
@@ -104,7 +110,32 @@ public class VOSpaceClient {
                .POST(HttpRequest.BodyPublishers.ofString(marshal(transfer)))
                .build();

        return call(request, BodyHandlers.ofInputStream(), 200, res -> unmarshal(res, Transfer.class)).getProtocols();
        List<Protocol> protocols = new ArrayList<>();

        HttpResponse<InputStream> redirectResponse = call(request, BodyHandlers.ofInputStream(), 200, (res, previousRes) -> {
            Transfer transferRes = unmarshal(res, Transfer.class);
            protocols.addAll(transferRes.getProtocols());
            // IMPORTANT: HTTP call for checking the error status (in case of empty protocols list) must be
            // performed outside this lambda otherwise the exception "IllegalStateException: No thread-bound request found"
            // will be thrown. For this reason the previous response data is returned here and checked later.
            return previousRes;
        });

        if (protocols.isEmpty()) {
            redirectResponse.headers().firstValue("Location").ifPresent(url -> {
                Pattern pattern = Pattern.compile(".*/transfers/(.+)/results/transferDetails");
                Matcher matcher = pattern.matcher(url);
                if (matcher.matches()) {
                    String jobId = matcher.group(1);
                    String errorDetail = getErrorDetail(jobId);
                    if (!errorDetail.isBlank()) {
                        throw new BadRequestException(errorDetail);
                    }
                }
            });
            throw new BadRequestException("Protocol negotiation failed");
        }
        return protocols.get(0).getEndpoint();
    }

    public Node createNode(Node node) {
@@ -170,16 +201,25 @@ public class VOSpaceClient {
    }

    private <T, U> U call(HttpRequest request, HttpResponse.BodyHandler<T> responseBodyHandler, int expectedStatusCode, Function<T, U> responseHandler) {
        return call(request, responseBodyHandler, expectedStatusCode, (res, prev) -> {
            return responseHandler.apply(res);
        });
    }

    private <T, U> U call(HttpRequest request, HttpResponse.BodyHandler<T> responseBodyHandler, int expectedStatusCode, BiFunction<T, HttpResponse<T>, U> responseHandler) {
        try {
            return httpClient.sendAsync(request, responseBodyHandler)
                    .thenApply(response -> {
                        if (response.statusCode() == expectedStatusCode) {
                            return response.body();
                            return response;
                        }
                        logServerError(request, response);
                        throw new VOSpaceException("Error calling " + request.uri().toString() + ". Server response code is " + response.statusCode());
                    })
                    .thenApplyAsync(response -> responseHandler.apply(response), jaxbExecutor)
                    .thenApplyAsync(response -> {
                        HttpResponse<T> prev = response.previousResponse().orElse(null);
                        return responseHandler.apply(response.body(), prev);
                    }, jaxbExecutor)
                    .join();
        } catch (CompletionException ex) {
            if (ex.getCause() != null) {
+1 −1
Original line number Diff line number Diff line
@@ -158,7 +158,7 @@ public class JobController extends BaseController {
        protocol.setUri("ivo://ivoa.net/vospace/core#httpput");
        transfer.getProtocols().add(protocol);

        return client.getFileServiceEndpoints(transfer).get(0).getEndpoint();
        return client.getFileServiceEndpoint(transfer);
    }

    private void upload(String endpoint, String content) {
+1 −1
Original line number Diff line number Diff line
@@ -80,7 +80,7 @@ public class NodesController extends BaseController {
        protocol.setUri("ivo://ivoa.net/vospace/core#httpget");
        transfer.getProtocols().add(protocol);

        String url = client.getFileServiceEndpoints(transfer).get(0).getEndpoint();
        String url = client.getFileServiceEndpoint(transfer);
        HttpHeaders headers = new HttpHeaders();
        headers.set("Location", url);
        return new ResponseEntity<>(headers, HttpStatus.SEE_OTHER);
+1 −1
Original line number Diff line number Diff line
@@ -96,6 +96,6 @@ public class UploadController extends BaseController {
        protocol.setUri("ivo://ivoa.net/vospace/core#httpput");
        transfer.getProtocols().add(protocol);

        return client.getFileServiceEndpoints(transfer).get(0).getEndpoint();
        return client.getFileServiceEndpoint(transfer);
    }
}
+82 −1
Original line number Diff line number Diff line
@@ -5,17 +5,23 @@
 */
package it.inaf.ia2.vospace.ui.client;

import it.inaf.ia2.vospace.ui.exception.BadRequestException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.net.http.HttpClient;
import java.net.http.HttpHeaders;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import javax.servlet.http.HttpServletRequest;
import net.ivoa.xml.vospace.v2.ContainerNode;
import net.ivoa.xml.vospace.v2.Protocol;
import net.ivoa.xml.vospace.v2.Transfer;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -104,6 +110,81 @@ public class VOSpaceClientTest {
        assertEquals("error message", voSpaceClient.getErrorDetail("123"));
    }

    @Test
    public void testGetFileServiceEndpointSuccess() throws Exception {

        Transfer transfer = new Transfer();
        transfer.setDirection("pushToVoSpace");
        transfer.setTarget("vos://ia2.inaf.it!vospace/mynode");

        Protocol protocol = new Protocol();
        protocol.setUri("ivo://ivoa.net/vospace/core#httpput");
        transfer.getProtocols().add(protocol);

        CompletableFuture response = getMockedStreamResponseFuture(200, getResourceFileContent("transfer-response-ok.xml"));
        when(mockedHttpClient.sendAsync(any(), any())).thenReturn(response);

        assertEquals("http://storage1.example.com/trans/mynode", voSpaceClient.getFileServiceEndpoint(transfer));
    }

    @Test
    public void testGetFileServiceEndpointError() {

        Transfer transfer = new Transfer();
        transfer.setDirection("pushToVoSpace");
        transfer.setTarget("vos://ia2.inaf.it!vospace/mynode");

        Protocol protocol = new Protocol();
        protocol.setUri("ivo://ivoa.net/vospace/core#httpput");
        transfer.getProtocols().add(protocol);

        HttpResponse<InputStream> redirectResponse = mock(HttpResponse.class);
        HttpHeaders headers = mock(HttpHeaders.class);
        when(headers.firstValue("Location")).thenReturn(Optional.of("/vospace/transfers/1234/results/transferDetails"));
        when(redirectResponse.headers()).thenReturn(headers);

        HttpResponse<InputStream> mockedStreamResponse = getMockedStreamResponse(200, getResourceFileContent("transfer-response-no-protocols.xml"));
        when(mockedStreamResponse.previousResponse()).thenReturn(Optional.of(redirectResponse));

        CompletableFuture response1 = CompletableFuture.completedFuture(mockedStreamResponse);
        CompletableFuture response2 = getMockedStringResponseFuture(200, "error message");
        when(mockedHttpClient.sendAsync(any(), any())).thenReturn(response1).thenReturn(response2);

        BadRequestException ex = assertThrows(BadRequestException.class, () -> {
            voSpaceClient.getFileServiceEndpoint(transfer);
        });
        assertEquals("error message", ex.getMessage());
    }

    @Test
    public void testGetFileServiceEndpointErrorWithoutMessage() {

        Transfer transfer = new Transfer();
        transfer.setDirection("pushToVoSpace");
        transfer.setTarget("vos://ia2.inaf.it!vospace/mynode");

        Protocol protocol = new Protocol();
        protocol.setUri("ivo://ivoa.net/vospace/core#httpput");
        transfer.getProtocols().add(protocol);

        HttpResponse<InputStream> redirectResponse = mock(HttpResponse.class);
        HttpHeaders headers = mock(HttpHeaders.class);
        when(headers.firstValue("Location")).thenReturn(Optional.of("/vospace/transfers/1234/results/transferDetails"));
        when(redirectResponse.headers()).thenReturn(headers);

        HttpResponse<InputStream> mockedStreamResponse = getMockedStreamResponse(200, getResourceFileContent("transfer-response-no-protocols.xml"));
        when(mockedStreamResponse.previousResponse()).thenReturn(Optional.of(redirectResponse));

        CompletableFuture response1 = CompletableFuture.completedFuture(mockedStreamResponse);
        CompletableFuture response2 = getMockedStringResponseFuture(200, "");
        when(mockedHttpClient.sendAsync(any(), any())).thenReturn(response1).thenReturn(response2);

        BadRequestException ex = assertThrows(BadRequestException.class, () -> {
            voSpaceClient.getFileServiceEndpoint(transfer);
        });
        assertEquals("Protocol negotiation failed", ex.getMessage());
    }

    protected static String getResourceFileContent(String fileName) {
        try ( InputStream in = VOSpaceClientTest.class.getClassLoader().getResourceAsStream(fileName)) {
            return new String(in.readAllBytes(), StandardCharsets.UTF_8);
Loading