Loading vospace-ui-backend/pom.xml +6 −1 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ <properties> <java.version>14</java.version> <mockito.version>3.5.13</mockito.version> </properties> <dependencies> Loading @@ -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> Loading @@ -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> Loading vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/JaxbForkJoinWorkerThreadFactory.java 0 → 100644 +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); } } } vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/VOSpaceUiApplication.java +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; Loading @@ -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); } } vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/client/VOSpaceClient.java +6 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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("/")) { Loading @@ -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) Loading @@ -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) { Loading vospace-ui-backend/src/test/java/it/inaf/ia2/vospace/ui/client/VOSpaceClientTest.java 0 → 100644 +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
vospace-ui-backend/pom.xml +6 −1 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ <properties> <java.version>14</java.version> <mockito.version>3.5.13</mockito.version> </properties> <dependencies> Loading @@ -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> Loading @@ -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> Loading
vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/JaxbForkJoinWorkerThreadFactory.java 0 → 100644 +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); } } }
vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/VOSpaceUiApplication.java +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; Loading @@ -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); } }
vospace-ui-backend/src/main/java/it/inaf/ia2/vospace/ui/client/VOSpaceClient.java +6 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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("/")) { Loading @@ -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) Loading @@ -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) { Loading
vospace-ui-backend/src/test/java/it/inaf/ia2/vospace/ui/client/VOSpaceClientTest.java 0 → 100644 +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; } }