diff --git a/afs-server/src/main/java/ch/ethz/sis/afsserver/server/impl/ApiServerAdapter.java b/afs-server/src/main/java/ch/ethz/sis/afsserver/server/impl/ApiServerAdapter.java index d8ce01b6482aee8051b22c42888736491477c6bf..20aed622dd6e6ce3803a4b94f6e111cda171950e 100644 --- a/afs-server/src/main/java/ch/ethz/sis/afsserver/server/impl/ApiServerAdapter.java +++ b/afs-server/src/main/java/ch/ethz/sis/afsserver/server/impl/ApiServerAdapter.java @@ -5,6 +5,7 @@ import ch.ethz.sis.afsserver.http.*; import ch.ethz.sis.afsserver.server.*; import ch.ethz.sis.afsserver.server.performance.Event; import ch.ethz.sis.afsserver.server.performance.PerformanceAuditor; +import ch.ethz.sis.shared.io.IOUtils; import ch.ethz.sis.shared.json.JSONObjectMapper; import ch.ethz.sis.shared.log.LogManager; import ch.ethz.sis.shared.log.Logger; @@ -43,7 +44,7 @@ public class ApiServerAdapter<CONNECTION, API> implements HttpServerHandler { String transactionManagerKey = null; Map<String, Object> methodParameters = new HashMap<>(); for (Map.Entry<String, List<String>> entry:uriParameters.entrySet()) { - Object value = null; + String value = null; if (entry.getValue() != null) { if (entry.getValue().size() == 1) { value = entry.getValue().get(0); @@ -54,16 +55,31 @@ public class ApiServerAdapter<CONNECTION, API> implements HttpServerHandler { switch (entry.getKey()) { case "method": - method = (String) value; + method = value; break; case "sessionToken": - sessionToken = (String) value; + sessionToken = value; break; case "interactiveSessionKey": - interactiveSessionKey = (String) value; + interactiveSessionKey = value; break; case "transactionManagerKey": - transactionManagerKey = (String) value; + transactionManagerKey = value; + break; + case "transactionId": + methodParameters.put(entry.getKey(), UUID.fromString(value)); + break; + case "recursively": + methodParameters.put(entry.getKey(), Boolean.valueOf(value)); + break; + case "offset": + methodParameters.put(entry.getKey(), Long.valueOf(value)); + break; + case "limit": + methodParameters.put(entry.getKey(), Integer.valueOf(value)); + break; + case "md5Hash": + methodParameters.put(entry.getKey(), IOUtils.decodeBase64(value)); break; default: methodParameters.put(entry.getKey(), value); @@ -71,6 +87,10 @@ public class ApiServerAdapter<CONNECTION, API> implements HttpServerHandler { } } + if (method.equals("write")) { + methodParameters.put("data", requestBody); + } + ApiRequest apiRequest = new ApiRequest("1", method, methodParameters, sessionToken, interactiveSessionKey, transactionManagerKey); Response response = server.processOperation(apiRequest, apiResponseBuilder, performanceAuditor); HttpResponse httpResponse = getHTTPResponse(response); diff --git a/afs-server/src/test/java/ch/ethz/sis/afsserver/impl/APIServerAdapterWrapper.java b/afs-server/src/test/java/ch/ethz/sis/afsserver/impl/APIServerAdapterWrapper.java new file mode 100644 index 0000000000000000000000000000000000000000..301484634415e3c955dca573e25dc0c24c7834b0 --- /dev/null +++ b/afs-server/src/test/java/ch/ethz/sis/afsserver/impl/APIServerAdapterWrapper.java @@ -0,0 +1,91 @@ +package ch.ethz.sis.afsserver.impl; + + +import ch.ethz.sis.afsserver.core.AbstractPublicAPIWrapper; +import ch.ethz.sis.afsserver.http.HttpResponse; +import ch.ethz.sis.afsserver.server.impl.ApiRequest; +import ch.ethz.sis.afsserver.server.impl.ApiResponse; +import ch.ethz.sis.afsserver.server.impl.ApiServerAdapter; +import ch.ethz.sis.afsserver.server.performance.PerformanceAuditor; +import ch.ethz.sis.shared.io.IOUtils; +import ch.ethz.sis.shared.json.JSONObjectMapper; +import ch.ethz.sis.shared.log.LogManager; +import ch.ethz.sis.shared.log.Logger; +import io.netty.handler.codec.http.HttpMethod; + +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; +import java.util.*; + +import static ch.ethz.sis.afsserver.http.HttpResponse.CONTENT_TYPE_BINARY_DATA; +import static ch.ethz.sis.afsserver.http.HttpResponse.CONTENT_TYPE_JSON; + +public class APIServerAdapterWrapper extends AbstractPublicAPIWrapper { + + private static final Logger logger = LogManager.getLogger(APIServerAdapterWrapper.class); + + private ApiServerAdapter apiServerAdapter; + private JSONObjectMapper jsonObjectMapper; + + public APIServerAdapterWrapper(ApiServerAdapter apiServerAdapter, JSONObjectMapper jsonObjectMapper) { + this.apiServerAdapter = apiServerAdapter; + this.jsonObjectMapper = jsonObjectMapper; + } + + public Map<String, List<String>> getURIParameters(Map<String, Object> args) { + Map<String, List<String>> result = new HashMap<>(args.size()); + for (Map.Entry<String, Object> entry:args.entrySet()) { + if (entry.getKey().equals("data") && entry.getValue() instanceof byte[]) { + continue; // Skip + } else if(entry.getValue() instanceof byte[]) { + result.put(entry.getKey(), List.of(IOUtils.encodeBase64((byte[]) entry.getValue()))); + } else { + result.put(entry.getKey(), List.of(String.valueOf(entry.getValue()))); + } + } + return result; + } + + public <E> E process(String method, Map<String, Object> args) { + try { + HttpMethod httpMethod = null; + switch (method){ + case "delete": + httpMethod = HttpMethod.DELETE; + break; + case "write": + httpMethod = HttpMethod.PUT; + break; + case "list": + case "read": + case "isSessionValid": + httpMethod = HttpMethod.GET; + break; + default: + httpMethod = HttpMethod.POST; + } + + Map<String, List<String>> uriParameters = getURIParameters(args); + uriParameters.put("method", List.of(method)); + uriParameters.put("sessionToken", List.of(UUID.randomUUID().toString())); + + byte[] requestBody = null; + if (method.equals("write")) { + requestBody = (byte[]) args.get("data"); + } + + HttpResponse response = apiServerAdapter.process(httpMethod, uriParameters, requestBody); + switch (response.getContentType()) { + case CONTENT_TYPE_BINARY_DATA: + return (E) response.getBody(); + case CONTENT_TYPE_JSON: + ApiResponse apiResponse = jsonObjectMapper.readValue(new ByteArrayInputStream(response.getBody()), ApiResponse.class); + return (E) apiResponse.getResult(); + } + } catch (Throwable throwable) { + throw new RuntimeException(throwable); + } + throw new RuntimeException("This line should be unreachable"); + } + +} diff --git a/afs-server/src/test/java/ch/ethz/sis/afsserver/impl/PublicAPIAdapterWrapper.java b/afs-server/src/test/java/ch/ethz/sis/afsserver/impl/APIServerWrapper.java similarity index 82% rename from afs-server/src/test/java/ch/ethz/sis/afsserver/impl/PublicAPIAdapterWrapper.java rename to afs-server/src/test/java/ch/ethz/sis/afsserver/impl/APIServerWrapper.java index 42e5ba48c0aae18b147d3dde1db0305ee8603751..356e728da2f50e97fc6f6602e8f1c2f380a6aa7c 100644 --- a/afs-server/src/test/java/ch/ethz/sis/afsserver/impl/PublicAPIAdapterWrapper.java +++ b/afs-server/src/test/java/ch/ethz/sis/afsserver/impl/APIServerWrapper.java @@ -13,15 +13,15 @@ import ch.ethz.sis.shared.log.Logger; import java.util.Map; import java.util.UUID; -public class PublicAPIAdapterWrapper extends AbstractPublicAPIWrapper { +public class APIServerWrapper extends AbstractPublicAPIWrapper { - private static final Logger logger = LogManager.getLogger(PublicAPIAdapterWrapper.class); + private static final Logger logger = LogManager.getLogger(APIServerWrapper.class); private APIServer apiServer; private final ApiResponseBuilder apiResponseBuilder; - public PublicAPIAdapterWrapper(APIServer apiServerAdapter) { - this.apiServer = apiServerAdapter; + public APIServerWrapper(APIServer apiServer) { + this.apiServer = apiServer; this.apiResponseBuilder = new ApiResponseBuilder(); } diff --git a/afs-server/src/test/java/ch/ethz/sis/afsserver/impl/ApiServerAdapterTest.java b/afs-server/src/test/java/ch/ethz/sis/afsserver/impl/ApiServerAdapterTest.java new file mode 100644 index 0000000000000000000000000000000000000000..6067da289e4d5eb6868c1ac8242c71de41c5c47f --- /dev/null +++ b/afs-server/src/test/java/ch/ethz/sis/afsserver/impl/ApiServerAdapterTest.java @@ -0,0 +1,21 @@ +package ch.ethz.sis.afsserver.impl; + +import ch.ethz.sis.afsserver.ServerClientEnvironmentFS; +import ch.ethz.sis.afsserver.api.PublicAPI; +import ch.ethz.sis.afsserver.server.APIServer; +import ch.ethz.sis.afsserver.server.impl.ApiServerAdapter; +import ch.ethz.sis.afsserver.startup.AtomicFileSystemServerParameter; +import ch.ethz.sis.shared.json.JSONObjectMapper; +import ch.ethz.sis.shared.startup.Configuration; + +public class ApiServerAdapterTest extends ApiServerTest { + + @Override + public PublicAPI getPublicAPI() throws Exception { + APIServer apiServer = getAPIServer(); + Configuration configuration = ServerClientEnvironmentFS.getInstance().getDefaultServerConfiguration(); + JSONObjectMapper jsonObjectMapper = configuration.getSharableInstance(AtomicFileSystemServerParameter.jsonObjectMapperClass); + ApiServerAdapter apiServerAdapter = new ApiServerAdapter(apiServer, jsonObjectMapper); + return new APIServerAdapterWrapper(apiServerAdapter, jsonObjectMapper); + } +} diff --git a/afs-server/src/test/java/ch/ethz/sis/afsserver/impl/ApiServerTest.java b/afs-server/src/test/java/ch/ethz/sis/afsserver/impl/ApiServerTest.java index e88563710e71a09aaee239a3c912eefe9ebbde9f..878ced130896a38df4ec6b9a9339c1ab98030316 100644 --- a/afs-server/src/test/java/ch/ethz/sis/afsserver/impl/ApiServerTest.java +++ b/afs-server/src/test/java/ch/ethz/sis/afsserver/impl/ApiServerTest.java @@ -15,8 +15,7 @@ import ch.ethz.sis.shared.startup.Configuration; public class ApiServerTest extends PublicApiTest { - @Override - public PublicAPI getPublicAPI() throws Exception { + protected APIServer getAPIServer() throws Exception { Configuration configuration = ServerClientEnvironmentFS.getInstance().getDefaultServerConfiguration(); ConnectionFactory connectionFactory = new ConnectionFactory(); @@ -35,6 +34,11 @@ public class ApiServerTest extends PublicApiTest { observer.init(configuration); APIServer apiServer = new APIServer(connectionsPool, workersPool, PublicAPI.class, interactiveSessionKey, transactionManagerKey, apiServerWorkerTimeout, observer); observer.init(apiServer, configuration); - return new PublicAPIAdapterWrapper(apiServer); + return apiServer; + } + + @Override + public PublicAPI getPublicAPI() throws Exception { + return new APIServerWrapper(getAPIServer()); } } diff --git a/afs/build.gradle b/afs/build.gradle index 89f23edfc9a2570f8196e49be65ee78ea9ba51c9..e9fc6a1dc7286f834adbd59fc556e30c349d190f 100644 --- a/afs/build.gradle +++ b/afs/build.gradle @@ -19,7 +19,8 @@ dependencies { 'log4j:log4j-core:2.10.0', 'fasterxml:jackson-annotations:2.9.10', 'fasterxml:jackson-core:2.9.10', - 'fasterxml:jackson-databind:2.9.10.8'; + 'fasterxml:jackson-databind:2.9.10.8', + 'fasterxml:jackson-datatype-jsr310:2.9.10'; testImplementation 'junit:junit:4.10' testRuntimeOnly 'hamcrest:hamcrest-core:1.3' } diff --git a/afs/src/main/java/ch/ethz/sis/shared/io/IOUtils.java b/afs/src/main/java/ch/ethz/sis/shared/io/IOUtils.java index 95562011cd0cdd3a471934fdaab0ee66717a8b47..4f1b4a076e6271ca9a7964eacb69cd34c405d834 100644 --- a/afs/src/main/java/ch/ethz/sis/shared/io/IOUtils.java +++ b/afs/src/main/java/ch/ethz/sis/shared/io/IOUtils.java @@ -47,10 +47,7 @@ import java.nio.file.attribute.UserPrincipal; import java.security.MessageDigest; import java.time.OffsetDateTime; import java.time.ZoneId; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; public class IOUtils { @@ -617,6 +614,14 @@ public class IOUtils { } } + public static String encodeBase64(byte[] input) { + return Base64.getEncoder().encodeToString(input); + } + + public static byte[] decodeBase64(String input) { + return Base64.getDecoder().decode(input); + } + public static boolean exists(String source) { Path sourcePath = getPathObject(source); return Files.exists(sourcePath); diff --git a/afs/src/main/java/ch/ethz/sis/shared/json/jackson/JSONObjectMapper.java b/afs/src/main/java/ch/ethz/sis/shared/json/jackson/JSONObjectMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..9684c26621a0c7026f5c51e5a8b81991595eca90 --- /dev/null +++ b/afs/src/main/java/ch/ethz/sis/shared/json/jackson/JSONObjectMapper.java @@ -0,0 +1,16 @@ +package ch.ethz.sis.shared.json.jackson; + +import java.io.InputStream; +import java.nio.charset.Charset; + +public interface JSONObjectMapper { + + Charset getCharset(); + + void setCharset(Charset charset); + + <T> T readValue(InputStream src, Class<T> valueType) throws Exception; + + byte[] writeValue(Object value) throws Exception; + +} diff --git a/afs/src/main/java/ch/ethz/sis/shared/json/jackson/JacksonObjectMapper.java b/afs/src/main/java/ch/ethz/sis/shared/json/jackson/JacksonObjectMapper.java index 9f46f8cb2d11587d20b2fde27cc1b559addd67bd..40b16605109b693ee4e32c91703d712a0861d4a9 100644 --- a/afs/src/main/java/ch/ethz/sis/shared/json/jackson/JacksonObjectMapper.java +++ b/afs/src/main/java/ch/ethz/sis/shared/json/jackson/JacksonObjectMapper.java @@ -20,6 +20,7 @@ import ch.ethz.sis.shared.json.JSONObjectMapper; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import java.io.InputStream; @@ -49,6 +50,7 @@ public class JacksonObjectMapper implements JSONObjectMapper public JacksonObjectMapper() { objectMapper = new ObjectMapper(); + objectMapper.registerModule(new JavaTimeModule()); // new module, NOT JSR310Module objectMapper.enable(SerializationFeature.INDENT_OUTPUT); objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); objectMapper.enableDefaultTyping();