diff --git a/api-data-store-server-java/src/main/java/ch/ethz/sis/afsclient/client/AfsClient.java b/api-data-store-server-java/src/main/java/ch/ethz/sis/afsclient/client/AfsClient.java index 991bfd26bdc869aa79abf9ec81d6dc75af4de1e8..b7f1376048736af6608849715d34d183eee84586 100644 --- a/api-data-store-server-java/src/main/java/ch/ethz/sis/afsclient/client/AfsClient.java +++ b/api-data-store-server-java/src/main/java/ch/ethz/sis/afsclient/client/AfsClient.java @@ -1,11 +1,14 @@ package ch.ethz.sis.afsclient.client; +import ch.ethz.sis.afsapi.api.PublicAPI; +import ch.ethz.sis.afsapi.dto.ApiResponse; +import ch.ethz.sis.afsapi.dto.File; +import ch.ethz.sis.afsclient.client.exception.ClientExceptions; +import ch.ethz.sis.afsjson.JsonObjectMapper; +import ch.ethz.sis.afsjson.jackson.JacksonObjectMapper; +import lombok.NonNull; + import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.net.Authenticator; -import java.net.PasswordAuthentication; import java.net.URI; import java.net.URLEncoder; import java.net.http.HttpClient; @@ -16,14 +19,6 @@ import java.time.Duration; import java.util.*; import java.util.stream.Stream; -import ch.ethz.sis.afsapi.api.PublicAPI; -import ch.ethz.sis.afsapi.dto.ApiResponse; -import ch.ethz.sis.afsapi.dto.File; -import ch.ethz.sis.afsclient.client.exception.ClientExceptions; -import ch.ethz.sis.afsjson.JsonObjectMapper; -import ch.ethz.sis.afsjson.jackson.JacksonObjectMapper; -import lombok.NonNull; - public final class AfsClient implements PublicAPI { @@ -43,7 +38,7 @@ public final class AfsClient implements PublicAPI private final URI serverUri; - private final JsonObjectMapper jsonObjectMapper; + private static final JsonObjectMapper jsonObjectMapper = new JacksonObjectMapper(); public AfsClient(final URI serverUri) { @@ -55,7 +50,6 @@ public final class AfsClient implements PublicAPI this.maxReadSizeInBytes = maxReadSizeInBytes; this.timeout = timeout; this.serverUri = serverUri; - this.jsonObjectMapper = new JacksonObjectMapper(); } public URI getServerUri() @@ -107,9 +101,8 @@ public final class AfsClient implements PublicAPI public @NonNull String login(@NonNull final String userId, @NonNull final String password) throws Exception { - Map<String, String> credentials = Map.of("userId", userId, "password", password); - String result = request("POST", "login", Map.of(), - jsonObjectMapper.writeValue(credentials)); + String result = request("POST", + "login", String.class, Map.of("userId", userId, "password", password)); setSessionToken(result); return result; } @@ -118,15 +111,14 @@ public final class AfsClient implements PublicAPI public @NonNull Boolean isSessionValid() throws Exception { validateSessionToken(); - return request("GET", "isSessionValid", Map.of("sessionToken", getSessionToken())); + return request("GET", "isSessionValid", Boolean.class,Map.of()); } @Override public @NonNull Boolean logout() throws Exception { validateSessionToken(); - Boolean result = request("POST", "logout", Map.of(), - jsonObjectMapper.writeValue(Map.of("sessionToken", getSessionToken()))); + Boolean result = request("POST", "logout", Boolean.class, Map.of()); setSessionToken(null); return result; } @@ -136,9 +128,9 @@ public final class AfsClient implements PublicAPI @NonNull final Boolean recursively) throws Exception { validateSessionToken(); - return request("GET", "list", + return request("GET", "list", List.class, Map.of("owner", owner, "source", source, "recursively", - recursively.toString(), "sessionToken", getSessionToken())); + recursively.toString())); } @Override @@ -146,24 +138,19 @@ public final class AfsClient implements PublicAPI @NonNull final Long offset, @NonNull final Integer limit) throws Exception { validateSessionToken(); - return request("GET", "read", + return request("GET", "read", byte[].class, Map.of("owner", owner, "source", source, "offset", - offset.toString(), "limit", limit.toString(), "sessionToken", - getSessionToken())); + offset.toString(), "limit", limit.toString())); } @Override public @NonNull Boolean write(@NonNull final String owner, @NonNull final String source, - @NonNull final Long offset, final byte @NonNull [] data, - final byte @NonNull [] md5Hash) throws Exception + @NonNull final Long offset, @NonNull final byte[] data, + @NonNull final byte[] md5Hash) throws Exception { validateSessionToken(); - Map<String, Object> parameters = - Map.of("owner", owner, "source", source, - "offset", offset, "data", data, "md5Hash", md5Hash, - "sessionToken", getSessionToken()); - - return request("POST", "write", Map.of(), jsonObjectMapper.writeValue(parameters)); + return request("POST", "write", Boolean.class, Map.of("owner", owner, "source", source, + "offset", offset.toString(), "data", Base64.getEncoder().encodeToString(data), "md5Hash", Base64.getEncoder().encodeToString(md5Hash))); } @Override @@ -171,11 +158,7 @@ public final class AfsClient implements PublicAPI throws Exception { validateSessionToken(); - Map<String, Object> parameters = - Map.of("owner", owner, "source", source, - "sessionToken", getSessionToken()); - - return request("DELETE", "delete", Map.of(), jsonObjectMapper.writeValue(parameters)); + return request("DELETE", "delete", Boolean.class, Map.of("owner", owner, "source", source)); } @Override @@ -185,12 +168,8 @@ public final class AfsClient implements PublicAPI throws Exception { validateSessionToken(); - Map<String, Object> parameters = - Map.of("sourceOwner", sourceOwner, "source", source, - "targetOwner", targetOwner, "target", target, - "sessionToken", getSessionToken()); - - return request("POST", "copy", Map.of(), jsonObjectMapper.writeValue(parameters)); + return request("POST", "copy", Boolean.class, Map.of("sourceOwner", sourceOwner, "source", source, + "targetOwner", targetOwner, "target", target)); } @Override @@ -200,91 +179,107 @@ public final class AfsClient implements PublicAPI throws Exception { validateSessionToken(); - Map<String, Object> parameters = - Map.of("sourceOwner", sourceOwner, "source", source, - "targetOwner", targetOwner, "target", target, - "sessionToken", getSessionToken()); - - return request("POST", "move", Map.of(), jsonObjectMapper.writeValue(parameters)); + return request("POST", "move", Boolean.class, Map.of("sourceOwner", sourceOwner, "source", source, + "targetOwner", targetOwner, "target", target)); } @Override public void begin(final UUID transactionId) throws Exception { validateSessionToken(); - Map<String, Object> parameters = - Map.of("transactionId", transactionId.toString(), - "sessionToken", getSessionToken(), - "interactiveSessionKey", getInteractiveSessionKey()); - request("POST", "begin", Map.of(), jsonObjectMapper.writeValue(parameters)); + request("POST", "begin", null, Map.of("transactionId", transactionId.toString())); } @Override public Boolean prepare() throws Exception { validateSessionToken(); - Map<String, Object> parameters = - Map.of("interactiveSessionKey", getInteractiveSessionKey(), - "transactionManagerKey", getTransactionManagerKey()); - return request("POST", "prepare", Map.of(), jsonObjectMapper.writeValue(parameters)); + return request("POST", "prepare", Boolean.class, Map.of()); } @Override public void commit() throws Exception { validateSessionToken(); - Map<String, Object> parameters = - Map.of("interactiveSessionKey", getInteractiveSessionKey()); - request("POST", "commit", Map.of(), jsonObjectMapper.writeValue(parameters)); + request("POST", "commit", null, Map.of()); } @Override public void rollback() throws Exception { validateSessionToken(); - Map<String, Object> parameters = - Map.of("interactiveSessionKey", getInteractiveSessionKey()); - request("POST", "rollback", Map.of(), jsonObjectMapper.writeValue(parameters)); + request("POST", "rollback", null, Map.of()); } @Override public List<UUID> recover() throws Exception { validateSessionToken(); - Map<String, Object> parameters = - Map.of("interactiveSessionKey", getInteractiveSessionKey(), - "transactionManagerKey", getTransactionManagerKey()); - return request("POST", "recover", Map.of(), jsonObjectMapper.writeValue(parameters)); + return request("POST", "recover", List.class, Map.of()); } - private <T> T request(@NonNull final String httpMethod, @NonNull final String apiMethod, - @NonNull final Map<String, String> parameters) throws Exception - { - return request(httpMethod, apiMethod, parameters, new byte[0]); - } @SuppressWarnings({ "OptionalGetWithoutIsPresent", "unchecked" }) - private <T> T request(@NonNull final String httpMethod, @NonNull final String apiMethod, - @NonNull final Map<String, String> parameters, final byte @NonNull [] body) + private <T> T request(@NonNull final String httpMethod, @NonNull final String apiMethod, Class<T> responseType, + @NonNull Map<String, String> params) throws Exception { - HttpClient.Builder clientBuilder = HttpClient.newBuilder() - .version(HttpClient.Version.HTTP_1_1) - .followRedirects(HttpClient.Redirect.NORMAL) - .connectTimeout(Duration.ofMillis(timeout)); - Map<String, String> params = parameters; + // + // General Parameter Handling + // - HttpClient client = clientBuilder.build(); + HashMap<String, String> mutableParams = new HashMap<>(params); + params = mutableParams; + + if (sessionToken != null) + { + params.put("sessionToken", sessionToken); + } + + if(interactiveSessionKey != null) + { + params.put("interactiveSessionKey", interactiveSessionKey); + } + + if (transactionManagerKey != null) + { + params.put("transactionManagerKey", transactionManagerKey); + } - final String query = Stream.concat( + String parameters = Stream.concat( Stream.of(new AbstractMap.SimpleImmutableEntry<>("method", apiMethod)), params.entrySet().stream()) - .map(entry -> urlEncode(entry.getKey()) + "=" + urlEncode(entry.getValue())) + .map(entry-> { + return urlEncode(entry.getKey()) + "=" + urlEncode(entry.getValue()); + }) .reduce((s1, s2) -> s1 + "&" + s2).get(); + // + // GET Request - Parameters on the query string + // + + String queryParameters = null; + if (httpMethod.equals("GET")) { + queryParameters = parameters; + } + + // + // POST and DELETE Request - Parameters on body + // + + byte[] body = null; + if (httpMethod.equals("POST") || httpMethod.equals("DELETE")) { + body = parameters.getBytes(StandardCharsets.UTF_8); + } else { + body = new byte[0]; + } + + // + // HTTP Client + // final URI uri = new URI(serverUri.getScheme(), null, serverUri.getHost(), serverUri.getPort(), - serverUri.getPath(), query, null); + serverUri.getPath(), queryParameters, null); HttpRequest.Builder builder = HttpRequest.newBuilder() .uri(uri) @@ -294,6 +289,13 @@ public final class AfsClient implements PublicAPI final HttpRequest request = builder.build(); + HttpClient.Builder clientBuilder = HttpClient.newBuilder() + .version(HttpClient.Version.HTTP_1_1) + .followRedirects(HttpClient.Redirect.NORMAL) + .connectTimeout(Duration.ofMillis(timeout)); + + HttpClient client = clientBuilder.build(); + final HttpResponse<byte[]> httpResponse = client.send(request, HttpResponse.BodyHandlers.ofByteArray()); @@ -305,18 +307,10 @@ public final class AfsClient implements PublicAPI throw new IllegalArgumentException( "Server error HTTP response. Missing content-type"); } - String content = httpResponse.headers().map().get("content-type").get(0); + String contentType = httpResponse.headers().map().get("content-type").get(0); + byte[] responseBody = httpResponse.body(); - switch (content) - { - case "application/json": - return parseJsonResponse(httpResponse); - case "application/octet-stream": - return (T) httpResponse.body(); - default: - throw new IllegalArgumentException( - "Client error HTTP response. Unsupported content-type received."); - } + return getResponseResult(responseType, contentType, responseBody); } else if (statusCode >= 400 && statusCode < 500) { // jsonObjectMapper can't deserialize immutable lists sent in the error message. @@ -331,10 +325,40 @@ public final class AfsClient implements PublicAPI } } - private <T> T parseJsonResponse(final HttpResponse<byte[]> httpResponse) throws Exception + public static <T> T getResponseResult(Class<T> responseType, String contentType, byte[] responseBody) + throws Exception + { + switch (contentType) + { + case "text/plain": + return AfsClient.parseFormDataResponse(responseType, responseBody); + case "application/json": + return AfsClient.parseJsonResponse(responseBody); + case "application/octet-stream": + return (T) responseBody; + default: + throw new IllegalArgumentException( + "Client error HTTP response. Unsupported content-type received."); + } + } + + private static <T> T parseFormDataResponse(Class<T> responseType, byte[] responseBody) + { + if (responseType == null) { + return null; + } else if (responseType == String.class) { + return responseType.cast(new String(responseBody, StandardCharsets.UTF_8)); + } else if (responseType == Boolean.class) { + return responseType.cast(Boolean.parseBoolean(new String(responseBody, StandardCharsets.UTF_8))); + } + + throw new IllegalStateException("Unreachable statement!"); + } + + private static <T> T parseJsonResponse(byte[] responseBody) throws Exception { final ApiResponse response = - jsonObjectMapper.readValue(new ByteArrayInputStream(httpResponse.body()), + jsonObjectMapper.readValue(new ByteArrayInputStream(responseBody), ApiResponse.class); if (response.getError() != null) @@ -353,5 +377,4 @@ public final class AfsClient implements PublicAPI throw new IllegalStateException("No session information detected!"); } } - } diff --git a/api-data-store-server-java/src/main/java/ch/ethz/sis/afsclient/client/AfsClientV2.java b/api-data-store-server-java/src/main/java/ch/ethz/sis/afsclient/client/AfsClientV2.java deleted file mode 100644 index aa456946a960d8562a75a5d28c8700730cfb07f8..0000000000000000000000000000000000000000 --- a/api-data-store-server-java/src/main/java/ch/ethz/sis/afsclient/client/AfsClientV2.java +++ /dev/null @@ -1,376 +0,0 @@ -package ch.ethz.sis.afsclient.client; - -import ch.ethz.sis.afsapi.api.PublicAPI; -import ch.ethz.sis.afsapi.dto.ApiResponse; -import ch.ethz.sis.afsapi.dto.File; -import ch.ethz.sis.afsclient.client.exception.ClientExceptions; -import ch.ethz.sis.afsjson.JsonObjectMapper; -import ch.ethz.sis.afsjson.jackson.JacksonObjectMapper; -import lombok.NonNull; - -import java.io.ByteArrayInputStream; -import java.math.BigInteger; -import java.net.URI; -import java.net.URLEncoder; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.util.*; -import java.util.stream.Stream; - -public final class AfsClientV2 implements PublicAPI -{ - - private static final int DEFAULT_PACKAGE_SIZE_IN_BYTES = 1024; - - private static final int DEFAULT_TIMEOUT_IN_MILLIS = 30000; - - private final int maxReadSizeInBytes; - - private final int timeout; - - private String sessionToken; - - private String interactiveSessionKey; - - private String transactionManagerKey; - - private final URI serverUri; - - private static final JsonObjectMapper jsonObjectMapper = new JacksonObjectMapper(); - - public AfsClientV2(final URI serverUri) - { - this(serverUri, DEFAULT_PACKAGE_SIZE_IN_BYTES, DEFAULT_TIMEOUT_IN_MILLIS); - } - - public AfsClientV2(final URI serverUri, final int maxReadSizeInBytes, final int timeout) - { - this.maxReadSizeInBytes = maxReadSizeInBytes; - this.timeout = timeout; - this.serverUri = serverUri; - } - - public URI getServerUri() - { - return serverUri; - } - - public int getMaxReadSizeInBytes() - { - return maxReadSizeInBytes; - } - - public String getSessionToken() - { - return sessionToken; - } - - public void setSessionToken(final String sessionToken) - { - this.sessionToken = sessionToken; - } - - public String getInteractiveSessionKey() - { - return interactiveSessionKey; - } - - public void setInteractiveSessionKey(String interactiveSessionKey) - { - this.interactiveSessionKey = interactiveSessionKey; - } - - public String getTransactionManagerKey() - { - return transactionManagerKey; - } - - public void setTransactionManagerKey(String transactionManagerKey) - { - this.transactionManagerKey = transactionManagerKey; - } - - private static String urlEncode(final String s) - { - return URLEncoder.encode(s, StandardCharsets.UTF_8); - } - - @Override - public @NonNull String login(@NonNull final String userId, @NonNull final String password) - throws Exception - { - String result = request("POST", - "login", String.class, Map.of("userId", userId, "password", password)); - setSessionToken(result); - return result; - } - - @Override - public @NonNull Boolean isSessionValid() throws Exception - { - validateSessionToken(); - return request("GET", "isSessionValid", Boolean.class,Map.of()); - } - - @Override - public @NonNull Boolean logout() throws Exception - { - validateSessionToken(); - Boolean result = request("POST", "logout", Boolean.class, Map.of()); - setSessionToken(null); - return result; - } - - @Override - public @NonNull List<File> list(@NonNull final String owner, @NonNull final String source, - @NonNull final Boolean recursively) throws Exception - { - validateSessionToken(); - return request("GET", "list", List.class, - Map.of("owner", owner, "source", source, "recursively", - recursively.toString())); - } - - @Override - public @NonNull byte[] read(@NonNull final String owner, @NonNull final String source, - @NonNull final Long offset, @NonNull final Integer limit) throws Exception - { - validateSessionToken(); - return request("GET", "read", byte[].class, - Map.of("owner", owner, "source", source, "offset", - offset.toString(), "limit", limit.toString())); - } - - @Override - public @NonNull Boolean write(@NonNull final String owner, @NonNull final String source, - @NonNull final Long offset, @NonNull final byte[] data, - @NonNull final byte[] md5Hash) throws Exception - { - validateSessionToken(); - return request("POST", "write", Boolean.class, Map.of("owner", owner, "source", source, - "offset", offset.toString(), "data", Base64.getEncoder().encodeToString(data), "md5Hash", Base64.getEncoder().encodeToString(md5Hash))); - } - - @Override - public @NonNull Boolean delete(@NonNull final String owner, @NonNull final String source) - throws Exception - { - validateSessionToken(); - return request("DELETE", "delete", Boolean.class, Map.of("owner", owner, "source", source)); - } - - @Override - public @NonNull Boolean copy(@NonNull final String sourceOwner, @NonNull final String source, - @NonNull final String targetOwner, - @NonNull final String target) - throws Exception - { - validateSessionToken(); - return request("POST", "copy", Boolean.class, Map.of("sourceOwner", sourceOwner, "source", source, - "targetOwner", targetOwner, "target", target)); - } - - @Override - public @NonNull Boolean move(@NonNull final String sourceOwner, @NonNull final String source, - @NonNull final String targetOwner, - @NonNull final String target) - throws Exception - { - validateSessionToken(); - return request("POST", "move", Boolean.class, Map.of("sourceOwner", sourceOwner, "source", source, - "targetOwner", targetOwner, "target", target)); - } - - @Override - public void begin(final UUID transactionId) throws Exception - { - validateSessionToken(); - request("POST", "begin", null, Map.of("transactionId", transactionId.toString())); - } - - @Override - public Boolean prepare() throws Exception - { - validateSessionToken(); - return request("POST", "prepare", Boolean.class, Map.of()); - } - - @Override - public void commit() throws Exception - { - validateSessionToken(); - request("POST", "commit", null, Map.of()); - } - - @Override - public void rollback() throws Exception - { - validateSessionToken(); - request("POST", "rollback", null, Map.of()); - } - - @Override - public List<UUID> recover() throws Exception - { - validateSessionToken(); - return request("POST", "recover", List.class, Map.of()); - } - - - @SuppressWarnings({ "OptionalGetWithoutIsPresent", "unchecked" }) - private <T> T request(@NonNull final String httpMethod, @NonNull final String apiMethod, Class<T> responseType, - @NonNull final Map<String, String> params) - throws Exception - { - // - // General Parameter Handling - // - - if (sessionToken != null) - { - params.put("sessionToken", sessionToken); - } - - if(interactiveSessionKey != null) - { - params.put("interactiveSessionKey", interactiveSessionKey); - } - - if (transactionManagerKey != null) - { - params.put("transactionManagerKey", transactionManagerKey); - } - - String parameters = Stream.concat( - Stream.of(new AbstractMap.SimpleImmutableEntry<>("method", apiMethod)), - params.entrySet().stream()) - .map(entry-> { - return urlEncode(entry.getKey()) + "=" + urlEncode(entry.getValue()); - }) - .reduce((s1, s2) -> s1 + "&" + s2).get(); - - // - // GET Request - Parameters on the query string - // - - String queryParameters = null; - if (httpMethod.equals("GET")) { - queryParameters = parameters; - } - - // - // POST Request - Parameters on body - // - - byte[] body = null; - if (httpMethod.equals("POST")) { - body = parameters.getBytes(StandardCharsets.UTF_8); - } - - // - // HTTP Client - // - final URI uri = - new URI(serverUri.getScheme(), null, serverUri.getHost(), serverUri.getPort(), - serverUri.getPath(), queryParameters, null); - - HttpRequest.Builder builder = HttpRequest.newBuilder() - .uri(uri) - .version(HttpClient.Version.HTTP_1_1) - .timeout(Duration.ofMillis(timeout)) - .method(httpMethod, HttpRequest.BodyPublishers.ofByteArray(body)); - - final HttpRequest request = builder.build(); - - HttpClient.Builder clientBuilder = HttpClient.newBuilder() - .version(HttpClient.Version.HTTP_1_1) - .followRedirects(HttpClient.Redirect.NORMAL) - .connectTimeout(Duration.ofMillis(timeout)); - - HttpClient client = clientBuilder.build(); - - final HttpResponse<byte[]> httpResponse = - client.send(request, HttpResponse.BodyHandlers.ofByteArray()); - - final int statusCode = httpResponse.statusCode(); - if (statusCode >= 200 && statusCode < 300) - { - if (!httpResponse.headers().map().containsKey("content-type")) - { - throw new IllegalArgumentException( - "Server error HTTP response. Missing content-type"); - } - String contentType = httpResponse.headers().map().get("content-type").get(0); - byte[] responseBody = httpResponse.body(); - - return getResponseResult(responseType, contentType, responseBody); - } else if (statusCode >= 400 && statusCode < 500) - { - // jsonObjectMapper can't deserialize immutable lists sent in the error message. - String res = new String(httpResponse.body(), StandardCharsets.UTF_8); - throw ClientExceptions.API_ERROR.getInstance(res); - } else if (statusCode >= 500 && statusCode < 600) - { - throw ClientExceptions.SERVER_ERROR.getInstance(statusCode); - } else - { - throw ClientExceptions.OTHER_ERROR.getInstance(statusCode); - } - } - - public static <T> T getResponseResult(Class<T> responseType, String contentType, byte[] responseBody) - throws Exception - { - switch (contentType) - { - case "text/plain": - return AfsClientV2.parseFormDataResponse(responseType, responseBody); - case "application/json": - return AfsClientV2.parseJsonResponse(responseBody); - case "application/octet-stream": - return (T) responseBody; - default: - throw new IllegalArgumentException( - "Client error HTTP response. Unsupported content-type received."); - } - } - - private static <T> T parseFormDataResponse(Class<T> responseType, byte[] responseBody) - { - if (responseType == null) { - return null; - } else if (responseType == String.class) { - return responseType.cast(new String(responseBody, StandardCharsets.UTF_8)); - } else if (responseType == Boolean.class) { - return responseType.cast(Boolean.parseBoolean(new String(responseBody, StandardCharsets.UTF_8))); - } - - throw new IllegalStateException("Unreachable statement!"); - } - - private static <T> T parseJsonResponse(byte[] responseBody) throws Exception - { - final ApiResponse response = - jsonObjectMapper.readValue(new ByteArrayInputStream(responseBody), - ApiResponse.class); - - if (response.getError() != null) - { - throw ClientExceptions.API_ERROR.getInstance(response.getError()); - } else - { - return (T) response.getResult(); - } - } - - private void validateSessionToken() - { - if (getSessionToken() == null) - { - throw new IllegalStateException("No session information detected!"); - } - } -} diff --git a/server-data-store/src/main/java/ch/ethz/sis/afsserver/http/impl/NettyHttpHandler.java b/server-data-store/src/main/java/ch/ethz/sis/afsserver/http/impl/NettyHttpHandler.java index 50099d69e6814d9989fbe65405320b8403ef096c..30e08bc01fb665f47aa86b16a187039de83789de 100644 --- a/server-data-store/src/main/java/ch/ethz/sis/afsserver/http/impl/NettyHttpHandler.java +++ b/server-data-store/src/main/java/ch/ethz/sis/afsserver/http/impl/NettyHttpHandler.java @@ -27,7 +27,6 @@ import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.handler.codec.http.*; import java.nio.charset.StandardCharsets; -import java.util.Base64; import java.util.Set; import static io.netty.handler.codec.http.HttpMethod.*; @@ -59,18 +58,27 @@ public class NettyHttpHandler extends ChannelInboundHandlerAdapter if (msg instanceof FullHttpRequest) { final FullHttpRequest request = (FullHttpRequest) msg; - QueryStringDecoder queryStringDecoder = new QueryStringDecoder(request.uri(), true); - if (queryStringDecoder.path().equals(uri) && + QueryStringDecoder queryStringDecoderForPath = new QueryStringDecoder(request.uri(), true); + + if (queryStringDecoderForPath.path().equals(uri) && allowedMethods.contains(request.method())) { FullHttpResponse response = null; ByteBuf content = request.content(); try { + QueryStringDecoder queryStringDecoderForParameters = null; byte[] array = new byte[content.readableBytes()]; content.readBytes(array); + + if (GET.equals(request.method())) { + queryStringDecoderForParameters = queryStringDecoderForPath; + } else { + queryStringDecoderForParameters = new QueryStringDecoder(new String(array, StandardCharsets.UTF_8), StandardCharsets.UTF_8, false); + } + HttpResponse apiResponse = httpServerHandler.process(request.method(), - queryStringDecoder.parameters(), array); + queryStringDecoderForParameters.parameters(), null); HttpResponseStatus status = (!apiResponse.isError()) ? HttpResponseStatus.OK : HttpResponseStatus.BAD_REQUEST; diff --git a/server-data-store/src/main/java/ch/ethz/sis/afsserver/http/impl/NettyHttpHandlerV2.java b/server-data-store/src/main/java/ch/ethz/sis/afsserver/http/impl/NettyHttpHandlerV2.java deleted file mode 100644 index 5cb098e0a0626802c1fb869b180ff5fdd8154c89..0000000000000000000000000000000000000000 --- a/server-data-store/src/main/java/ch/ethz/sis/afsserver/http/impl/NettyHttpHandlerV2.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright ETH 2022 - 2023 Zürich, Scientific IT Services - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ch.ethz.sis.afsserver.http.impl; - -import ch.ethz.sis.afsserver.http.HttpResponse; -import ch.ethz.sis.afsserver.http.HttpServerHandler; -import ch.ethz.sis.shared.log.LogManager; -import ch.ethz.sis.shared.log.Logger; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; -import io.netty.channel.ChannelFutureListener; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInboundHandlerAdapter; -import io.netty.handler.codec.http.*; - -import java.nio.charset.StandardCharsets; -import java.util.Set; - -import static io.netty.handler.codec.http.HttpMethod.*; - -public class NettyHttpHandlerV2 extends ChannelInboundHandlerAdapter -{ - - private static final Logger logger = LogManager.getLogger(NettyHttpServer.class); - - private static final byte[] NOT_FOUND = "404 NOT FOUND".getBytes(); - - private static final ByteBuf NOT_FOUND_BUFFER = Unpooled.wrappedBuffer(NOT_FOUND); - - private static final Set<HttpMethod> allowedMethods = Set.of(GET, POST, PUT, DELETE); - - private final String uri; - - private final HttpServerHandler httpServerHandler; - - public NettyHttpHandlerV2(String uri, HttpServerHandler httpServerHandler) - { - this.uri = uri; - this.httpServerHandler = httpServerHandler; - } - - @Override - public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception - { - if (msg instanceof FullHttpRequest) - { - final FullHttpRequest request = (FullHttpRequest) msg; - QueryStringDecoder queryStringDecoderForPath = new QueryStringDecoder(request.uri(), true); - - if (queryStringDecoderForPath.path().equals(uri) && - allowedMethods.contains(request.method())) - { - FullHttpResponse response = null; - ByteBuf content = request.content(); - try - { - QueryStringDecoder queryStringDecoderForParameters = null; - byte[] array = new byte[content.readableBytes()]; - content.readBytes(array); - - if (GET.equals(request.method())) { - queryStringDecoderForParameters = queryStringDecoderForPath; - } else { - queryStringDecoderForParameters = new QueryStringDecoder(new String(array, StandardCharsets.UTF_8), StandardCharsets.UTF_8, false); - } - - HttpResponse apiResponse = httpServerHandler.process(request.method(), - queryStringDecoderForParameters.parameters(), null); - HttpResponseStatus status = (!apiResponse.isError()) ? - HttpResponseStatus.OK : - HttpResponseStatus.BAD_REQUEST; - response = getHttpResponse( - status, - apiResponse.getContentType(), - Unpooled.wrappedBuffer(apiResponse.getBody()), - apiResponse.getBody().length); - ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); - } finally - { - content.release(); - } - } else - { - FullHttpResponse response = getHttpResponse( - HttpResponseStatus.NOT_FOUND, - "text/plain", - NOT_FOUND_BUFFER, - NOT_FOUND.length); - ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); - } - } else - { - super.channelRead(ctx, msg); - } - } - - @Override - public void channelReadComplete(ChannelHandlerContext ctx) throws Exception - { - ctx.flush(); - } - - @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception - { - logger.catching(cause); - byte[] causeBytes = cause.getMessage().getBytes(); - FullHttpResponse response = getHttpResponse( - HttpResponseStatus.INTERNAL_SERVER_ERROR, - "text/plain", - Unpooled.wrappedBuffer(causeBytes), - causeBytes.length - ); - ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); - } - - public FullHttpResponse getHttpResponse( - HttpResponseStatus status, - String contentType, - ByteBuf content, - int contentLength) - { - FullHttpResponse response = new DefaultFullHttpResponse( - HttpVersion.HTTP_1_1, - status, - content - ); - response.headers().set(HttpHeaderNames.CONTENT_LENGTH, contentLength); - response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN, "*"); - response.headers().set(HttpHeaderNames.CONTENT_TYPE, contentType); - response.headers().set(HttpHeaderNames.CONNECTION, "close"); - return response; - } -} diff --git a/server-data-store/src/main/java/ch/ethz/sis/afsserver/server/impl/ApiServerAdapter.java b/server-data-store/src/main/java/ch/ethz/sis/afsserver/server/impl/ApiServerAdapter.java index 312d301a6835ed1cd07424fc9b42df6c091a4fe4..a56cd2d14c0bb8a574ddb54a7a43eeecebf15605 100644 --- a/server-data-store/src/main/java/ch/ethz/sis/afsserver/server/impl/ApiServerAdapter.java +++ b/server-data-store/src/main/java/ch/ethz/sis/afsserver/server/impl/ApiServerAdapter.java @@ -15,19 +15,25 @@ */ package ch.ethz.sis.afsserver.server.impl; +import ch.ethz.sis.afsjson.JsonObjectMapper; import ch.ethz.sis.afsserver.exception.HTTPExceptions; -import ch.ethz.sis.afsserver.http.*; -import ch.ethz.sis.afsserver.server.*; +import ch.ethz.sis.afsserver.http.HttpResponse; +import ch.ethz.sis.afsserver.http.HttpServerHandler; +import ch.ethz.sis.afsserver.server.APIServer; +import ch.ethz.sis.afsserver.server.APIServerException; +import ch.ethz.sis.afsserver.server.Request; +import ch.ethz.sis.afsserver.server.Response; import ch.ethz.sis.afsserver.server.performance.Event; import ch.ethz.sis.afsserver.server.performance.PerformanceAuditor; -import ch.ethz.sis.afsjson.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 io.netty.handler.codec.http.HttpMethod.*; + /* * This class is supposed to be called by a TCP or HTTP transport class */ @@ -58,7 +64,7 @@ public class ApiServerAdapter<CONNECTION, API> implements HttpServerHandler case "list": case "read": case "isSessionValid": - return HttpMethod.GET; // all parameters from GET methods come on the query string + return GET; // all parameters from GET methods come on the query string case "write": case "move": case "copy": @@ -69,7 +75,7 @@ public class ApiServerAdapter<CONNECTION, API> implements HttpServerHandler case "commit": case "rollback": case "recover": - return HttpMethod.POST; // all parameters from POST methods come on the body + return POST; // all parameters from POST methods come on the body case "delete": return HttpMethod.DELETE; // all parameters from DELETE methods come on the body } @@ -82,7 +88,7 @@ public class ApiServerAdapter<CONNECTION, API> implements HttpServerHandler return correctMethod == givenMethod; } - public HttpResponse process(HttpMethod httpMethod, Map<String, List<String>> uriParameters, + public HttpResponse process(HttpMethod httpMethod, Map<String, List<String>> parameters, byte[] requestBody) { try @@ -90,129 +96,90 @@ public class ApiServerAdapter<CONNECTION, API> implements HttpServerHandler logger.traceAccess(null); PerformanceAuditor performanceAuditor = new PerformanceAuditor(); + + if (httpMethod != GET && httpMethod != POST && httpMethod != DELETE) + { + return getHTTPResponse(new ApiResponse("1", null, + HTTPExceptions.INVALID_HTTP_METHOD.getCause())); + } + String method = null; String sessionToken = null; String interactiveSessionKey = null; String transactionManagerKey = null; Map<String, Object> methodParameters = new HashMap<>(); - if (HttpMethod.GET.equals(httpMethod)) - { - for (Map.Entry<String, List<String>> entry : uriParameters.entrySet()) - { - String value = null; - if (entry.getValue() != null) - { - if (entry.getValue().size() == 1) - { - value = entry.getValue().get(0); - } else if (entry.getValue().size() > 1) - { - return getHTTPResponse(new ApiResponse("1", null, - HTTPExceptions.INVALID_PARAMETERS.getCause())); - } - } - try - { - switch (entry.getKey()) - { - case "method": - method = value; - if (!isValidMethod(httpMethod, method)) - { - return getHTTPResponse(new ApiResponse("1", null, - HTTPExceptions.INVALID_HTTP_METHOD.getCause())); - } - break; - case "sessionToken": - sessionToken = value; - break; - case "interactiveSessionKey": - interactiveSessionKey = value; - break; - case "transactionManagerKey": - 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; - default: - methodParameters.put(entry.getKey(), value); - break; - } - } catch (Exception e) - { - logger.catching(e); - return getHTTPResponse(new ApiResponse("1", null, - HTTPExceptions.INVALID_PARAMETERS.getCause( - e.getClass().getSimpleName(), - e.getMessage()))); - } - } - } else if (HttpMethod.POST.equals(httpMethod) || HttpMethod.DELETE.equals(httpMethod)) + for (Map.Entry<String, List<String>> entry : parameters.entrySet()) { - if (!uriParameters.containsKey("method")) + String value = null; + if (entry.getValue() != null) { - return getHTTPResponse(new ApiResponse("1", null, - HTTPExceptions.INVALID_HTTP_METHOD.getCause())); - } else - { - List<String> methodParam = uriParameters.get("method"); - if (methodParam == null || methodParam.size() != 1) + if (entry.getValue().size() == 1) + { + value = entry.getValue().get(0); + } else if (entry.getValue().size() > 1) { return getHTTPResponse(new ApiResponse("1", null, - HTTPExceptions.INVALID_HTTP_METHOD.getCause())); + HTTPExceptions.INVALID_PARAMETERS.getCause())); } - method = methodParam.get(0); } - if (!isValidMethod(httpMethod, method)) - { - return getHTTPResponse(new ApiResponse("1", null, - HTTPExceptions.INVALID_HTTP_METHOD.getCause())); - } - Map<String, Object> bodyParameterMap = readBody(requestBody, HashMap.class); - - for (Map.Entry<String, Object> entry : bodyParameterMap.entrySet()) + try { switch (entry.getKey()) { + case "method": + method = value; + if (!isValidMethod(httpMethod, method)) + { + return getHTTPResponse(new ApiResponse("1", null, + HTTPExceptions.INVALID_HTTP_METHOD.getCause())); + } + break; case "sessionToken": - sessionToken = (String) entry.getValue(); + sessionToken = value; break; case "interactiveSessionKey": - interactiveSessionKey = (String) entry.getValue(); + interactiveSessionKey = value; break; case "transactionManagerKey": - transactionManagerKey = (String) entry.getValue(); + transactionManagerKey = value; break; case "transactionId": - methodParameters.put(entry.getKey(), - UUID.fromString((String) entry.getValue())); + 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 "data": + methodParameters.put(entry.getKey(), Base64.getDecoder().decode(value)); + break; + case "md5Hash": + methodParameters.put(entry.getKey(), Base64.getDecoder().decode(value)); break; default: - methodParameters.put(entry.getKey(), entry.getValue()); + methodParameters.put(entry.getKey(), value); + break; } + } catch (Exception e) + { + logger.catching(e); + return getHTTPResponse(new ApiResponse("1", null, + HTTPExceptions.INVALID_PARAMETERS.getCause( + e.getClass().getSimpleName(), + e.getMessage()))); } - } else - { - return getHTTPResponse(new ApiResponse("1", null, - HTTPExceptions.INVALID_HTTP_METHOD.getCause())); } ApiRequest apiRequest = new ApiRequest("1", method, methodParameters, sessionToken, interactiveSessionKey, transactionManagerKey); - Response response = - server.processOperation(apiRequest, apiResponseBuilder, performanceAuditor); + Response response = server.processOperation(apiRequest, apiResponseBuilder, performanceAuditor); HttpResponse httpResponse = getHTTPResponse(response); performanceAuditor.audit(Event.WriteResponse); logger.traceExit(performanceAuditor); @@ -250,24 +217,24 @@ public class ApiServerAdapter<CONNECTION, API> implements HttpServerHandler return null; // This should never happen, it would mean an error writing the Unknown error happened. } - private <T> T readBody(byte[] requestBody, Class<T> valueType) throws Exception - { - return (T) jsonObjectMapper.readValue(new ByteArrayInputStream(requestBody), valueType); - } - - private HttpResponse getHTTPResponse(Response response) throws Exception + public HttpResponse getHTTPResponse(Response response) + throws Exception { boolean error = response.getError() != null; String contentType = null; byte[] body = null; - if (response.getResult() instanceof byte[]) - { - contentType = HttpResponse.CONTENT_TYPE_BINARY_DATA; - body = (byte[]) response.getResult(); - } else - { + if (error) { + contentType = HttpResponse.CONTENT_TYPE_JSON; + body = jsonObjectMapper.writeValue(response); + } else if (response.getResult() instanceof List) { contentType = HttpResponse.CONTENT_TYPE_JSON; body = jsonObjectMapper.writeValue(response); + } else if(response.getResult() instanceof byte[]) { + contentType = HttpResponse.CONTENT_TYPE_BINARY_DATA; + body = (byte[]) response.getResult(); + } else { + contentType = HttpResponse.CONTENT_TYPE_TEXT; + body = String.valueOf(response.getResult()).getBytes(StandardCharsets.UTF_8); } return new HttpResponse(error, contentType, body); } diff --git a/server-data-store/src/main/java/ch/ethz/sis/afsserver/server/impl/ApiServerAdapterV2.java b/server-data-store/src/main/java/ch/ethz/sis/afsserver/server/impl/ApiServerAdapterV2.java deleted file mode 100644 index 2b8806784208f9891f3c151b32206eb1f30deb85..0000000000000000000000000000000000000000 --- a/server-data-store/src/main/java/ch/ethz/sis/afsserver/server/impl/ApiServerAdapterV2.java +++ /dev/null @@ -1,239 +0,0 @@ -/* - * Copyright ETH 2022 - 2023 Zürich, Scientific IT Services - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ch.ethz.sis.afsserver.server.impl; - -import ch.ethz.sis.afsjson.JsonObjectMapper; -import ch.ethz.sis.afsserver.exception.HTTPExceptions; -import ch.ethz.sis.afsserver.http.HttpResponse; -import ch.ethz.sis.afsserver.http.HttpServerHandler; -import ch.ethz.sis.afsserver.server.APIServer; -import ch.ethz.sis.afsserver.server.APIServerException; -import ch.ethz.sis.afsserver.server.Request; -import ch.ethz.sis.afsserver.server.Response; -import ch.ethz.sis.afsserver.server.performance.Event; -import ch.ethz.sis.afsserver.server.performance.PerformanceAuditor; -import ch.ethz.sis.shared.log.LogManager; -import ch.ethz.sis.shared.log.Logger; -import io.netty.handler.codec.http.HttpMethod; - -import java.nio.charset.StandardCharsets; -import java.util.*; - -import static io.netty.handler.codec.http.HttpMethod.*; - -/* - * This class is supposed to be called by a TCP or HTTP transport class - */ -public class ApiServerAdapterV2<CONNECTION, API> implements HttpServerHandler -{ - - private static final Logger logger = LogManager.getLogger(ApiServerAdapterV2.class); - - private final APIServer<CONNECTION, Request, Response, API> server; - - private final JsonObjectMapper jsonObjectMapper; - - private final ApiResponseBuilder apiResponseBuilder; - - public ApiServerAdapterV2( - APIServer<CONNECTION, Request, Response, API> server, - JsonObjectMapper jsonObjectMapper) - { - this.server = server; - this.jsonObjectMapper = jsonObjectMapper; - this.apiResponseBuilder = new ApiResponseBuilder(); - } - - public static HttpMethod getHttpMethod(String apiMethod) - { - switch (apiMethod) - { - case "list": - case "read": - case "isSessionValid": - return GET; // all parameters from GET methods come on the query string - case "write": - case "move": - case "copy": - case "login": - case "logout": - case "begin": - case "prepare": - case "commit": - case "rollback": - case "recover": - return POST; // all parameters from POST methods come on the body - case "delete": - return HttpMethod.DELETE; // all parameters from DELETE methods come on the body - } - throw new UnsupportedOperationException("This line SHOULD NOT be unreachable!"); - } - - public boolean isValidMethod(HttpMethod givenMethod, String apiMethod) - { - HttpMethod correctMethod = getHttpMethod(apiMethod); - return correctMethod == givenMethod; - } - - public HttpResponse process(HttpMethod httpMethod, Map<String, List<String>> parameters, - byte[] requestBody) - { - try - { - logger.traceAccess(null); - PerformanceAuditor performanceAuditor = new PerformanceAuditor(); - - - if (httpMethod != GET && httpMethod != POST && httpMethod != DELETE) - { - return getHTTPResponse(new ApiResponse("1", null, - HTTPExceptions.INVALID_HTTP_METHOD.getCause())); - } - - String method = null; - String sessionToken = null; - String interactiveSessionKey = null; - String transactionManagerKey = null; - Map<String, Object> methodParameters = new HashMap<>(); - - for (Map.Entry<String, List<String>> entry : parameters.entrySet()) - { - String value = null; - if (entry.getValue() != null) - { - if (entry.getValue().size() == 1) - { - value = entry.getValue().get(0); - } else if (entry.getValue().size() > 1) - { - return getHTTPResponse(new ApiResponse("1", null, - HTTPExceptions.INVALID_PARAMETERS.getCause())); - } - } - - try - { - switch (entry.getKey()) - { - case "method": - method = value; - if (!isValidMethod(httpMethod, method)) - { - return getHTTPResponse(new ApiResponse("1", null, - HTTPExceptions.INVALID_HTTP_METHOD.getCause())); - } - break; - case "sessionToken": - sessionToken = value; - break; - case "interactiveSessionKey": - interactiveSessionKey = value; - break; - case "transactionManagerKey": - 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 "data": - methodParameters.put(entry.getKey(), Base64.getDecoder().decode(value)); - break; - case "md5Hash": - methodParameters.put(entry.getKey(), Base64.getDecoder().decode(value)); - break; - default: - methodParameters.put(entry.getKey(), value); - break; - } - } catch (Exception e) - { - logger.catching(e); - return getHTTPResponse(new ApiResponse("1", null, - HTTPExceptions.INVALID_PARAMETERS.getCause( - e.getClass().getSimpleName(), - e.getMessage()))); - } - } - - ApiRequest apiRequest = new ApiRequest("1", method, methodParameters, sessionToken, - interactiveSessionKey, transactionManagerKey); - Response response = server.processOperation(apiRequest, apiResponseBuilder, performanceAuditor); - HttpResponse httpResponse = getHTTPResponse(response); - performanceAuditor.audit(Event.WriteResponse); - logger.traceExit(performanceAuditor); - logger.traceExit(httpResponse); - return httpResponse; - } catch (APIServerException e) - { - logger.catching(e); - switch (e.getType()) - { - case MethodNotFound: - case IncorrectParameters: - case InternalError: - try - { - return getHTTPResponse(new ApiResponse("1", null, e.getData())); - } catch (Exception ex) - { - logger.catching(ex); - } - } - } catch (Exception e) - { - logger.catching(e); - try - { - return getHTTPResponse(new ApiResponse("1", null, - HTTPExceptions.UNKNOWN.getCause(e.getClass().getSimpleName(), - e.getMessage()))); - } catch (Exception ex) - { - logger.catching(ex); - } - } - return null; // This should never happen, it would mean an error writing the Unknown error happened. - } - - public HttpResponse getHTTPResponse(Response response) - throws Exception - { - boolean error = response.getError() != null; - String contentType = null; - byte[] body = null; - if (response.getResult() instanceof List) { - contentType = HttpResponse.CONTENT_TYPE_JSON; - body = jsonObjectMapper.writeValue(response); - } else if(response.getResult() instanceof byte[]) { - contentType = HttpResponse.CONTENT_TYPE_BINARY_DATA; - body = (byte[]) response.getResult(); - } else { - contentType = HttpResponse.CONTENT_TYPE_TEXT; - body = String.valueOf(response.getResult()).getBytes(StandardCharsets.UTF_8); - } - return new HttpResponse(error, contentType, body); - } - -} \ No newline at end of file diff --git a/server-data-store/src/test/java/ch/ethz/sis/afsserver/core/AbstractPublicAPIWrapper.java b/server-data-store/src/test/java/ch/ethz/sis/afsserver/core/AbstractPublicAPIWrapper.java index 27853db9e03455cbcc08c4b228ce21dbad4198af..6695bc3aeeddca9b0a40e7bdf06f0eb11a4babda 100644 --- a/server-data-store/src/test/java/ch/ethz/sis/afsserver/core/AbstractPublicAPIWrapper.java +++ b/server-data-store/src/test/java/ch/ethz/sis/afsserver/core/AbstractPublicAPIWrapper.java @@ -15,13 +15,10 @@ */ package ch.ethz.sis.afsserver.core; -import ch.ethz.sis.afsapi.dto.File; import ch.ethz.sis.afsapi.api.PublicAPI; +import ch.ethz.sis.afsapi.dto.File; import lombok.NonNull; -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.IOException; import java.util.List; import java.util.Map; import java.util.UUID; @@ -29,8 +26,7 @@ import java.util.UUID; public abstract class AbstractPublicAPIWrapper implements PublicAPI { - public abstract <E> E process(String method, Map<String, Object> queryParams, - Map<String, Object> bodyParams); + public abstract <E> E process(Class<E> responseType, String method, Map<String, Object> params); @Override public List<File> list(@NonNull String owner, @NonNull String source, @@ -40,7 +36,7 @@ public abstract class AbstractPublicAPIWrapper implements PublicAPI "owner", owner, "source", source, "recursively", recursively); - return process("list", args, Map.of()); + return process(List.class, "list", args); } @Override @@ -52,7 +48,7 @@ public abstract class AbstractPublicAPIWrapper implements PublicAPI "source", source, "offset", offset, "limit", limit); - return process("read", args, Map.of()); + return process(byte[].class,"read", args); } @Override @@ -65,7 +61,7 @@ public abstract class AbstractPublicAPIWrapper implements PublicAPI "offset", offset, "data", data, "md5Hash", md5Hash); - return process("write", Map.of(), args); + return process(Boolean.class, "write", args); } @Override @@ -74,7 +70,7 @@ public abstract class AbstractPublicAPIWrapper implements PublicAPI Map<String, Object> args = Map.of( "owner", owner, "source", source); - return process("delete", Map.of(), args); + return process(Boolean.class, "delete", args); } @Override @@ -86,7 +82,7 @@ public abstract class AbstractPublicAPIWrapper implements PublicAPI "source", source, "targetOwner", targetOwner, "target", target); - return process("copy", Map.of(), args); + return process(Boolean.class,"copy", args); } @Override @@ -98,7 +94,7 @@ public abstract class AbstractPublicAPIWrapper implements PublicAPI "source", source, "targetOwner", targetOwner, "target", target); - return process("move", Map.of(), args); + return process(Boolean.class, "move", args); } @Override diff --git a/server-data-store/src/test/java/ch/ethz/sis/afsserver/core/AbstractPublicAPIWrapperV2.java b/server-data-store/src/test/java/ch/ethz/sis/afsserver/core/AbstractPublicAPIWrapperV2.java deleted file mode 100644 index 661e8a2258ae02b7c290a9765617f992aaab4b26..0000000000000000000000000000000000000000 --- a/server-data-store/src/test/java/ch/ethz/sis/afsserver/core/AbstractPublicAPIWrapperV2.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright ETH 2022 - 2023 Zürich, Scientific IT Services - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ch.ethz.sis.afsserver.core; - -import ch.ethz.sis.afsapi.api.PublicAPI; -import ch.ethz.sis.afsapi.dto.File; -import lombok.NonNull; - -import java.util.List; -import java.util.Map; -import java.util.UUID; - -public abstract class AbstractPublicAPIWrapperV2 implements PublicAPI -{ - - public abstract <E> E process(Class<E> responseType, String method, Map<String, Object> params); - - @Override - public List<File> list(@NonNull String owner, @NonNull String source, - @NonNull Boolean recursively) throws Exception - { - Map<String, Object> args = Map.of( - "owner", owner, - "source", source, - "recursively", recursively); - return process(List.class, "list", args); - } - - @Override - public byte[] read(@NonNull String owner, @NonNull String source, @NonNull Long offset, - @NonNull Integer limit) throws Exception - { - Map<String, Object> args = Map.of( - "owner", owner, - "source", source, - "offset", offset, - "limit", limit); - return process(byte[].class,"read", args); - } - - @Override - public Boolean write(@NonNull String owner, @NonNull String source, @NonNull Long offset, - @NonNull byte[] data, @NonNull byte[] md5Hash) throws Exception - { - Map<String, Object> args = Map.of( - "owner", owner, - "source", source, - "offset", offset, - "data", data, - "md5Hash", md5Hash); - return process(Boolean.class, "write", args); - } - - @Override - public Boolean delete(@NonNull String owner, @NonNull String source) throws Exception - { - Map<String, Object> args = Map.of( - "owner", owner, - "source", source); - return process(Boolean.class, "delete", args); - } - - @Override - public Boolean copy(@NonNull String sourceOwner, @NonNull String source, - @NonNull String targetOwner, @NonNull String target) throws Exception - { - Map<String, Object> args = Map.of( - "sourceOwner", sourceOwner, - "source", source, - "targetOwner", targetOwner, - "target", target); - return process(Boolean.class,"copy", args); - } - - @Override - public Boolean move(@NonNull String sourceOwner, @NonNull String source, - @NonNull String targetOwner, @NonNull String target) throws Exception - { - Map<String, Object> args = Map.of( - "sourceOwner", sourceOwner, - "source", source, - "targetOwner", targetOwner, - "target", target); - return process(Boolean.class, "move", args); - } - - @Override - public void begin(UUID transactionId) throws Exception - { - //TODO: Unused - } - - @Override - public Boolean prepare() throws Exception - { - //TODO: Unused - return true; - } - - @Override - public void commit() throws Exception - { - //TODO: Unused - } - - @Override - public void rollback() throws Exception - { - //TODO: Unused - } - - @Override - public List<UUID> recover() throws Exception - { - //TODO: Unused - return null; - } - - @Override - public String login(String userId, String password) throws Exception - { - //TODO: Unused - return null; - } - - @Override - public Boolean isSessionValid() throws Exception - { - //TODO: Unused - return null; - } - - @Override - public Boolean logout() throws Exception - { - //TODO: Unused - return null; - } -} diff --git a/server-data-store/src/test/java/ch/ethz/sis/afsserver/impl/APIServerAdapterWrapper.java b/server-data-store/src/test/java/ch/ethz/sis/afsserver/impl/APIServerAdapterWrapper.java index b4fbd223a434ddb7172f7815edddfd79a91456eb..91972ae76949d1abb0a8dfcb27016aa78831e5eb 100644 --- a/server-data-store/src/test/java/ch/ethz/sis/afsserver/impl/APIServerAdapterWrapper.java +++ b/server-data-store/src/test/java/ch/ethz/sis/afsserver/impl/APIServerAdapterWrapper.java @@ -15,24 +15,19 @@ */ package ch.ethz.sis.afsserver.impl; +import ch.ethz.sis.afsclient.client.AfsClient; 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.afsjson.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; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; public class APIServerAdapterWrapper extends AbstractPublicAPIWrapper { @@ -41,13 +36,10 @@ public class APIServerAdapterWrapper extends AbstractPublicAPIWrapper private ApiServerAdapter apiServerAdapter; - private JsonObjectMapper jsonObjectMapper; - public APIServerAdapterWrapper(ApiServerAdapter apiServerAdapter, - JsonObjectMapper jsonObjectMapper) + public APIServerAdapterWrapper(ApiServerAdapter apiServerAdapter) { this.apiServerAdapter = apiServerAdapter; - this.jsonObjectMapper = jsonObjectMapper; } public Map<String, List<String>> getURIParameters(Map<String, Object> args) @@ -55,13 +47,9 @@ public class APIServerAdapterWrapper extends AbstractPublicAPIWrapper 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[]) + if (entry.getValue() instanceof byte[]) { - result.put(entry.getKey(), - List.of(IOUtils.encodeBase64((byte[]) entry.getValue()))); + result.put(entry.getKey(), List.of(IOUtils.encodeBase64((byte[]) entry.getValue()))); } else { result.put(entry.getKey(), List.of(String.valueOf(entry.getValue()))); @@ -70,49 +58,39 @@ public class APIServerAdapterWrapper extends AbstractPublicAPIWrapper return result; } - public <E> E process(String apiMethod, Map<String, Object> queryParams, - Map<String, Object> bodyParams) + public <E> E process(Class<E> responseType, String apiMethod, Map<String, Object> params) { try { HttpMethod httpMethod = ApiServerAdapter.getHttpMethod(apiMethod); - Map<String, List<String>> uriParameters = new HashMap<>(); + Map<String, List<String>> requestParameters = getURIParameters(params); + requestParameters.put("sessionToken", List.of(UUID.randomUUID().toString())); + requestParameters.put("method", List.of(apiMethod)); + byte[] requestBody = null; if (HttpMethod.GET.equals(httpMethod)) { - uriParameters = getURIParameters(queryParams); - uriParameters.put("sessionToken", List.of(UUID.randomUUID().toString())); + // Do nothing } else if (HttpMethod.POST.equals(httpMethod) || HttpMethod.DELETE.equals(httpMethod)) { - Map<String, Object> fullBodyParams = new HashMap<>(); - fullBodyParams.putAll(bodyParams); - fullBodyParams.put("sessionToken", UUID.randomUUID().toString()); - requestBody = jsonObjectMapper.writeValue(fullBodyParams); + // Do nothing } else { throw new IllegalArgumentException("Not supported HTTP method type!"); } - uriParameters.put("method", List.of(apiMethod)); - 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(); - } + + HttpResponse response = apiServerAdapter.process(httpMethod, requestParameters, null); + String contentType = response.getContentType(); + byte[] body = response.getBody(); + + return AfsClient.getResponseResult(responseType, contentType, body); } catch (Throwable throwable) { throw new RuntimeException(throwable); } - throw new RuntimeException("This line should be unreachable"); } } diff --git a/server-data-store/src/test/java/ch/ethz/sis/afsserver/impl/APIServerAdapterWrapperV2.java b/server-data-store/src/test/java/ch/ethz/sis/afsserver/impl/APIServerAdapterWrapperV2.java deleted file mode 100644 index 810d8460872b687d1ddf45997e3d369c2c7e9f03..0000000000000000000000000000000000000000 --- a/server-data-store/src/test/java/ch/ethz/sis/afsserver/impl/APIServerAdapterWrapperV2.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright ETH 2022 - 2023 Zürich, Scientific IT Services - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ch.ethz.sis.afsserver.impl; - -import ch.ethz.sis.afsclient.client.AfsClientV2; -import ch.ethz.sis.afsserver.core.AbstractPublicAPIWrapperV2; -import ch.ethz.sis.afsserver.http.HttpResponse; -import ch.ethz.sis.afsserver.server.impl.ApiServerAdapter; -import ch.ethz.sis.shared.io.IOUtils; -import ch.ethz.sis.shared.log.LogManager; -import ch.ethz.sis.shared.log.Logger; -import io.netty.handler.codec.http.HttpMethod; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -public class APIServerAdapterWrapperV2 extends AbstractPublicAPIWrapperV2 -{ - - private static final Logger logger = LogManager.getLogger(APIServerAdapterWrapperV2.class); - - private ApiServerAdapter apiServerAdapter; - - - public APIServerAdapterWrapperV2(ApiServerAdapter apiServerAdapter) - { - this.apiServerAdapter = apiServerAdapter; - } - - 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.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(Class<E> responseType, String apiMethod, Map<String, Object> params) - { - try - { - HttpMethod httpMethod = ApiServerAdapter.getHttpMethod(apiMethod); - Map<String, List<String>> requestParameters = getURIParameters(params); - requestParameters.put("sessionToken", List.of(UUID.randomUUID().toString())); - requestParameters.put("method", List.of(apiMethod)); - - byte[] requestBody = null; - - if (HttpMethod.GET.equals(httpMethod)) - { - // Do nothing - } else if (HttpMethod.POST.equals(httpMethod) || HttpMethod.DELETE.equals(httpMethod)) - { - // Do nothing - } else - { - throw new IllegalArgumentException("Not supported HTTP method type!"); - } - - - - HttpResponse response = apiServerAdapter.process(httpMethod, requestParameters, null); - String contentType = response.getContentType(); - byte[] body = response.getBody(); - - return AfsClientV2.getResponseResult(responseType, contentType, body); - } catch (Throwable throwable) - { - throw new RuntimeException(throwable); - } - } - -} diff --git a/server-data-store/src/test/java/ch/ethz/sis/afsserver/impl/APIServerWrapper.java b/server-data-store/src/test/java/ch/ethz/sis/afsserver/impl/APIServerWrapper.java index 4f0b7cbc308313a33c831180947f03111cfeeedb..4746825aef72aa1c7de900bacc3b24f90eb7cf5a 100644 --- a/server-data-store/src/test/java/ch/ethz/sis/afsserver/impl/APIServerWrapper.java +++ b/server-data-store/src/test/java/ch/ethz/sis/afsserver/impl/APIServerWrapper.java @@ -25,11 +25,11 @@ import ch.ethz.sis.afsserver.server.performance.PerformanceAuditor; import ch.ethz.sis.shared.log.LogManager; import ch.ethz.sis.shared.log.Logger; -import java.util.HashMap; import java.util.Map; import java.util.UUID; -public class APIServerWrapper extends AbstractPublicAPIWrapper { +public class APIServerWrapper extends AbstractPublicAPIWrapper +{ private static final Logger logger = LogManager.getLogger(APIServerWrapper.class); @@ -41,13 +41,10 @@ public class APIServerWrapper extends AbstractPublicAPIWrapper { this.apiResponseBuilder = new ApiResponseBuilder(); } - public <E> E process(String method, Map<String, Object> queryParams, Map<String, Object> bodyParams) { + public <E> E process(Class<E> responseType, String method, Map<String, Object> params) { PerformanceAuditor performanceAuditor = new PerformanceAuditor(); // Random Session token just works for tests with dummy authentication - Map<String, Object> args = new HashMap<>(); - args.putAll(queryParams); - args.putAll(bodyParams); - ApiRequest request = new ApiRequest("test", method, args, UUID.randomUUID().toString(), null, null); + ApiRequest request = new ApiRequest("test", method, params, UUID.randomUUID().toString(), null, null); try { Response response = apiServer.processOperation(request, apiResponseBuilder, performanceAuditor); diff --git a/server-data-store/src/test/java/ch/ethz/sis/afsserver/impl/APIServerWrapperV2.java b/server-data-store/src/test/java/ch/ethz/sis/afsserver/impl/APIServerWrapperV2.java deleted file mode 100644 index 6384b1ed15c5df46478c54964aebc0d37534c411..0000000000000000000000000000000000000000 --- a/server-data-store/src/test/java/ch/ethz/sis/afsserver/impl/APIServerWrapperV2.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright ETH 2022 - 2023 Zürich, Scientific IT Services - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ch.ethz.sis.afsserver.impl; - - -import ch.ethz.sis.afsserver.core.AbstractPublicAPIWrapperV2; -import ch.ethz.sis.afsserver.server.APIServer; -import ch.ethz.sis.afsserver.server.Response; -import ch.ethz.sis.afsserver.server.impl.ApiRequest; -import ch.ethz.sis.afsserver.server.impl.ApiResponseBuilder; -import ch.ethz.sis.afsserver.server.performance.PerformanceAuditor; -import ch.ethz.sis.shared.log.LogManager; -import ch.ethz.sis.shared.log.Logger; - -import java.util.Map; -import java.util.UUID; - -public class APIServerWrapperV2 extends AbstractPublicAPIWrapperV2 -{ - - private static final Logger logger = LogManager.getLogger(APIServerWrapperV2.class); - - private APIServer apiServer; - private final ApiResponseBuilder apiResponseBuilder; - - public APIServerWrapperV2(APIServer apiServer) { - this.apiServer = apiServer; - this.apiResponseBuilder = new ApiResponseBuilder(); - } - - public <E> E process(Class<E> responseType, String method, Map<String, Object> params) { - PerformanceAuditor performanceAuditor = new PerformanceAuditor(); - // Random Session token just works for tests with dummy authentication - ApiRequest request = new ApiRequest("test", method, params, UUID.randomUUID().toString(), null, null); - - try { - Response response = apiServer.processOperation(request, apiResponseBuilder, performanceAuditor); - return (E) response.getResult(); - } catch (Throwable throwable) { - throw new RuntimeException(throwable); - } - } - -} diff --git a/server-data-store/src/test/java/ch/ethz/sis/afsserver/impl/ApiServerAdapterTest.java b/server-data-store/src/test/java/ch/ethz/sis/afsserver/impl/ApiServerAdapterTest.java index e61913bf65df3447b73cb0669d68c9a09234f642..711a8bda4451a14f6b9b01081b829c5d352fb06a 100644 --- a/server-data-store/src/test/java/ch/ethz/sis/afsserver/impl/ApiServerAdapterTest.java +++ b/server-data-store/src/test/java/ch/ethz/sis/afsserver/impl/ApiServerAdapterTest.java @@ -31,6 +31,6 @@ public class ApiServerAdapterTest extends ApiServerTest { Configuration configuration = ServerClientEnvironmentFS.getInstance().getDefaultServerConfiguration(); JsonObjectMapper jsonObjectMapper = configuration.getSharableInstance(AtomicFileSystemServerParameter.jsonObjectMapperClass); ApiServerAdapter apiServerAdapter = new ApiServerAdapter(apiServer, jsonObjectMapper); - return new APIServerAdapterWrapper(apiServerAdapter, jsonObjectMapper); + return new APIServerAdapterWrapper(apiServerAdapter); } }