Skip to content
Snippets Groups Projects
Commit 55825cc6 authored by Sonia Zorba's avatar Sonia Zorba
Browse files

Solved JAXB ClassLoader issue. Added VOSpaceClientTest

parent 3f746155
Branches
Tags
No related merge requests found
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
<properties> <properties>
<java.version>14</java.version> <java.version>14</java.version>
<mockito.version>3.5.13</mockito.version>
</properties> </properties>
<dependencies> <dependencies>
...@@ -28,7 +29,6 @@ ...@@ -28,7 +29,6 @@
<artifactId>vospace-datamodel</artifactId> <artifactId>vospace-datamodel</artifactId>
<version>1.0-SNAPSHOT</version> <version>1.0-SNAPSHOT</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId> <artifactId>spring-boot-devtools</artifactId>
...@@ -40,6 +40,11 @@ ...@@ -40,6 +40,11 @@
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>
......
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);
}
}
}
package it.inaf.ia2.vospace.ui; package it.inaf.ia2.vospace.ui;
import java.util.concurrent.ForkJoinPool;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
...@@ -9,4 +10,13 @@ public class VOSpaceUiApplication { ...@@ -9,4 +10,13 @@ public class VOSpaceUiApplication {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(VOSpaceUiApplication.class, 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);
}
} }
...@@ -2,6 +2,7 @@ package it.inaf.ia2.vospace.ui.client; ...@@ -2,6 +2,7 @@ package it.inaf.ia2.vospace.ui.client;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import it.inaf.ia2.vospace.ui.VOSpaceException; import it.inaf.ia2.vospace.ui.VOSpaceException;
import it.inaf.ia2.vospace.ui.VOSpaceUiApplication;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.UncheckedIOException; import java.io.UncheckedIOException;
...@@ -13,6 +14,7 @@ import java.net.http.HttpResponse; ...@@ -13,6 +14,7 @@ import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers; import java.net.http.HttpResponse.BodyHandlers;
import java.util.Scanner; import java.util.Scanner;
import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionException;
import java.util.concurrent.ForkJoinPool;
import java.util.function.Function; import java.util.function.Function;
import javax.xml.bind.JAXB; import javax.xml.bind.JAXB;
import net.ivoa.xml.vospace.v2.Node; import net.ivoa.xml.vospace.v2.Node;
...@@ -33,6 +35,7 @@ public class VOSpaceClient { ...@@ -33,6 +35,7 @@ public class VOSpaceClient {
private final HttpClient httpClient; private final HttpClient httpClient;
private final String baseUrl; private final String baseUrl;
private final ForkJoinPool jaxbExecutor;
public VOSpaceClient(@Value("${vospace-backend-url}") String backendUrl) { public VOSpaceClient(@Value("${vospace-backend-url}") String backendUrl) {
if (backendUrl.endsWith("/")) { if (backendUrl.endsWith("/")) {
...@@ -41,6 +44,8 @@ public class VOSpaceClient { ...@@ -41,6 +44,8 @@ public class VOSpaceClient {
} }
baseUrl = backendUrl; baseUrl = backendUrl;
jaxbExecutor = VOSpaceUiApplication.getJaxbExecutor();
httpClient = HttpClient.newBuilder() httpClient = HttpClient.newBuilder()
.followRedirects(HttpClient.Redirect.ALWAYS) .followRedirects(HttpClient.Redirect.ALWAYS)
.version(HttpClient.Version.HTTP_1_1) .version(HttpClient.Version.HTTP_1_1)
...@@ -66,7 +71,7 @@ public class VOSpaceClient { ...@@ -66,7 +71,7 @@ public class VOSpaceClient {
logServerError(request, response); logServerError(request, response);
throw new VOSpaceException("Error calling " + request.uri().toString() + ". Server response code is " + response.statusCode()); 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(); .join();
} catch (CompletionException ex) { } catch (CompletionException ex) {
if (ex.getCause() != null) { if (ex.getCause() != null) {
......
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;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<vos:node xmlns:vos="http://www.ivoa.net/xml/VOSpace/v2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" uri="vos://ia2.inaf.it!vospace/node1" xsi:type="vos:ContainerNode">
<vos:properties>
<vos:property uri="ivo://ivoa.net/vospace/core#length" readOnly="true">483282612224</vos:property>
<vos:property uri="ivo://ivoa.net/vospace/core#date" readOnly="true">2018-11-23T19:30:03.387</vos:property>
<vos:property uri="ivo://ivoa.net/vospace/core#groupread" readOnly="false">ivo://ia2.inaf.it/gms#group1</vos:property>
<vos:property uri="ivo://ivoa.net/vospace/core#groupwrite" readOnly="false">ivo://ia2.inaf.it/gms#group1</vos:property>
<vos:property uri="ivo://ivoa.net/vospace/core#ispublic" readOnly="false">true</vos:property>
<vos:property uri="ivo://ivoa.net/vospace/core#creator" readOnly="false">123</vos:property>
<vos:property uri="ivo://ivoa.net/vospace/core#quota" readOnly="false">3298534883328</vos:property>
</vos:properties>
<vos:nodes>
<vos:node uri="vos://ia2.inaf.it!vospace/node1/node2" xsi:type="vos:ContainerNode">
<vos:properties>
<vos:property uri="ivo://ivoa.net/vospace/core#length" readOnly="true">483282612224</vos:property>
<vos:property uri="ivo://ivoa.net/vospace/core#date" readOnly="true">2018-11-23T19:30:03.350</vos:property>
<vos:property uri="ivo://ivoa.net/vospace/core#groupread" readOnly="false">ivo://ia2.inaf.it/gms#group1</vos:property>
<vos:property uri="ivo://ivoa.net/vospace/core#groupwrite" readOnly="false">ivo://ia2.inaf.it/gms#group1</vos:property>
<vos:property uri="ivo://ivoa.net/vospace/core#ispublic" readOnly="false">true</vos:property>
<vos:property uri="ivo://ivoa.net/vospace/core#creator" readOnly="false">123</vos:property>
</vos:properties>
<vos:nodes />
</vos:node>
</vos:nodes>
</vos:node>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment