Commit 55825cc6 authored by Sonia Zorba's avatar Sonia Zorba
Browse files

Solved JAXB ClassLoader issue. Added VOSpaceClientTest

parent 3f746155
Loading
Loading
Loading
Loading
+6 −1
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

    <properties>
        <java.version>14</java.version>
        <mockito.version>3.5.13</mockito.version>
    </properties>

    <dependencies>
@@ -28,7 +29,6 @@
            <artifactId>vospace-datamodel</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
@@ -40,6 +40,11 @@
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-inline</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
+32 −0
Original line number Diff line number Diff line
package it.inaf.ia2.vospace.ui;

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory;
import java.util.concurrent.ForkJoinWorkerThread;

/**
 * This class solves a ClassLoader issue with newer versions of Java. See this
 * post: https://stackoverflow.com/a/61012531/771431
 */
public class JaxbForkJoinWorkerThreadFactory implements ForkJoinWorkerThreadFactory {

    private final ClassLoader classLoader;

    public JaxbForkJoinWorkerThreadFactory() {
        classLoader = Thread.currentThread().getContextClassLoader();
    }

    @Override
    public final ForkJoinWorkerThread newThread(ForkJoinPool pool) {
        ForkJoinWorkerThread thread = new JaxbForkJoinWorkerThread(pool);
        thread.setContextClassLoader(classLoader);
        return thread;
    }

    private static class JaxbForkJoinWorkerThread extends ForkJoinWorkerThread {

        private JaxbForkJoinWorkerThread(ForkJoinPool pool) {
            super(pool);
        }
    }
}
+10 −0
Original line number Diff line number Diff line
package it.inaf.ia2.vospace.ui;

import java.util.concurrent.ForkJoinPool;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@@ -9,4 +10,13 @@ public class VOSpaceUiApplication {
    public static void main(String[] args) {
        SpringApplication.run(VOSpaceUiApplication.class, args);
    }

    /**
     * Solves a ClassLoader issue. See class JaxbForkJoinWorkerThreadFactory.
     */
    public static ForkJoinPool getJaxbExecutor() {
        JaxbForkJoinWorkerThreadFactory threadFactory = new JaxbForkJoinWorkerThreadFactory();
        int parallelism = Math.min(0x7fff /* copied from ForkJoinPool.java */, Runtime.getRuntime().availableProcessors());
        return new ForkJoinPool(parallelism, threadFactory, null, false);
    }
}
+6 −1
Original line number Diff line number Diff line
@@ -2,6 +2,7 @@ package it.inaf.ia2.vospace.ui.client;

import com.fasterxml.jackson.databind.ObjectMapper;
import it.inaf.ia2.vospace.ui.VOSpaceException;
import it.inaf.ia2.vospace.ui.VOSpaceUiApplication;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
@@ -13,6 +14,7 @@ import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.util.Scanner;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ForkJoinPool;
import java.util.function.Function;
import javax.xml.bind.JAXB;
import net.ivoa.xml.vospace.v2.Node;
@@ -33,6 +35,7 @@ public class VOSpaceClient {

    private final HttpClient httpClient;
    private final String baseUrl;
    private final ForkJoinPool jaxbExecutor;

    public VOSpaceClient(@Value("${vospace-backend-url}") String backendUrl) {
        if (backendUrl.endsWith("/")) {
@@ -41,6 +44,8 @@ public class VOSpaceClient {
        }
        baseUrl = backendUrl;

        jaxbExecutor = VOSpaceUiApplication.getJaxbExecutor();

        httpClient = HttpClient.newBuilder()
                .followRedirects(HttpClient.Redirect.ALWAYS)
                .version(HttpClient.Version.HTTP_1_1)
@@ -66,7 +71,7 @@ public class VOSpaceClient {
                        logServerError(request, response);
                        throw new VOSpaceException("Error calling " + request.uri().toString() + ". Server response code is " + response.statusCode());
                    })
                    .thenApply(response -> responseHandler.apply(response))
                    .thenApplyAsync(response -> responseHandler.apply(response), jaxbExecutor)
                    .join();
        } catch (CompletionException ex) {
            if (ex.getCause() != null) {
+80 −0
Original line number Diff line number Diff line
package it.inaf.ia2.vospace.ui.client;

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.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CompletableFuture;
import net.ivoa.xml.vospace.v2.ContainerNode;
import static org.junit.jupiter.api.Assertions.assertEquals;
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 org.mockito.MockedStatic;
import org.mockito.Mockito;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;

@ExtendWith(MockitoExtension.class)
public class VOSpaceClientTest {

    private HttpClient mockedHttpClient;
    private VOSpaceClient voSpaceClient;

    @BeforeEach
    public void init() {
        mockedHttpClient = mock(HttpClient.class);

        HttpClient.Builder builder = mock(HttpClient.Builder.class);
        when(builder.followRedirects(any())).thenReturn(builder);
        when(builder.version(any())).thenReturn(builder);
        when(builder.build()).thenReturn(mockedHttpClient);

        try ( MockedStatic<HttpClient> staticMock = Mockito.mockStatic(HttpClient.class)) {
            staticMock.when(HttpClient::newBuilder).thenReturn(builder);
            voSpaceClient = new VOSpaceClient("http://localhost/vospace");
        }
    }

    @Test
    public void testGetXmlNode() {
        ReflectionTestUtils.setField(voSpaceClient, "useJson", false);

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

        ContainerNode node = (ContainerNode) voSpaceClient.getNode("/node1");
        assertEquals("vos://ia2.inaf.it!vospace/node1", node.getUri());
    }

    protected static String getResourceFileContent(String fileName) {
        try ( InputStream in = VOSpaceClientTest.class.getClassLoader().getResourceAsStream(fileName)) {
            return new String(in.readAllBytes(), StandardCharsets.UTF_8);
        } catch (IOException ex) {
            throw new UncheckedIOException(ex);
        }
    }

    protected static CompletableFuture<HttpResponse<InputStream>> getMockedStreamResponseFuture(int statusCode, String body) {
        return CompletableFuture.completedFuture(getMockedStreamResponse(200, body));
    }

    protected static HttpResponse<InputStream> getMockedStreamResponse(int statusCode, String body) {
        HttpResponse response = getMockedResponse(statusCode);
        InputStream in = new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8));
        when(response.body()).thenReturn(in);
        return response;
    }

    protected static HttpResponse getMockedResponse(int statusCode) {
        HttpResponse response = mock(HttpResponse.class);
        when(response.statusCode()).thenReturn(statusCode);
        return response;
    }
}
Loading