diff --git a/api-data-store-server-java/build.gradle b/api-data-store-server-java/build.gradle index 340bf18c3b81b925f92fd8e732a6c5483b94374d..c770d54e3704c128ffeaa7f353a939fcde79c4fc 100644 --- a/api-data-store-server-java/build.gradle +++ b/api-data-store-server-java/build.gradle @@ -23,6 +23,6 @@ dependencies { annotationProcessor 'lombok:lombok:1.18.22' implementation project(':lib-json'), 'lombok:lombok:1.18.22' - testImplementation 'junit:junit:4.10' - testRuntimeOnly 'hamcrest:hamcrest-core:1.3' + testImplementation 'junit:junit:4.10', + 'hamcrest:hamcrest-core:1.3' } 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 2059a51fe5596e11fa81f7fb7a0666b4674bc8b0..63dc36dbcbea41f234b0348c8ba7348ff5bc47ea 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,6 +1,8 @@ package ch.ethz.sis.afsclient.client; import java.io.ByteArrayInputStream; +import java.net.Authenticator; +import java.net.PasswordAuthentication; import java.net.URI; import java.net.URLEncoder; import java.net.http.HttpClient; @@ -8,10 +10,7 @@ import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; import java.time.Duration; -import java.util.AbstractMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; +import java.util.*; import java.util.stream.Stream; import ch.ethz.sis.afsapi.api.PublicAPI; @@ -39,79 +38,109 @@ public final class AfsClient implements PublicAPI private final JsonObjectMapper jsonObjectMapper; - public AfsClient(final URI serverUri) { + public AfsClient(final URI serverUri) + { this(serverUri, DEFAULT_PACKAGE_SIZE_IN_BYTES, DEFAULT_TIMEOUT_IN_MILLIS); } - public AfsClient(final URI serverUri, final int maxReadSizeInBytes, final int timeout) { + public AfsClient(final URI serverUri, final int maxReadSizeInBytes, final int timeout) + { this.maxReadSizeInBytes = maxReadSizeInBytes; this.timeout = timeout; this.serverUri = serverUri; this.jsonObjectMapper = new JacksonObjectMapper(); } - public URI getServerUri() { + public URI getServerUri() + { return serverUri; } - public int getMaxReadSizeInBytes() { + public int getMaxReadSizeInBytes() + { return maxReadSizeInBytes; } - public String getSessionToken() { + public String getSessionToken() + { return sessionToken; } - public void setSessionToken(final String sessionToken) { + public void setSessionToken(final String sessionToken) + { this.sessionToken = sessionToken; } - private static String urlEncode(final String s) { + 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 { - return request("POST", "login", Map.of("userId", "admin", "password", "changeit")); + public @NonNull String login(@NonNull final String userId, @NonNull final String password) + throws Exception + { + String result = request("POST", "login", Map.of(), + (userId + ":" + password).getBytes()); + setSessionToken(result); + return result; } @Override - public @NonNull Boolean isSessionValid() throws Exception { - return null; + public @NonNull Boolean isSessionValid() throws Exception + { + if (getSessionToken() == null) + { + throw new IllegalStateException("No session information detected!"); + } + return request("GET", "isSessionValid", Map.of("sessionToken", getSessionToken())); } @Override - public @NonNull Boolean logout() throws Exception { - return null; + public @NonNull Boolean logout() throws Exception + { + if (getSessionToken() == null) + { + throw new IllegalStateException("No session information detected!"); + } +// Boolean result = request("POST", "logout", Map.of(), getSessionToken().getBytes()); + Boolean result = request("POST", "logout", Map.of("sessionToken", getSessionToken())); + setSessionToken(null); + return result; } @Override public @NonNull List<File> list(@NonNull final String owner, @NonNull final String source, - @NonNull final Boolean recursively) throws Exception { + @NonNull final Boolean recursively) throws Exception + { return null; } @Override - public byte @NonNull [] read(@NonNull final String owner, @NonNull final String source, - @NonNull final Long offset, @NonNull final Integer limit) throws Exception { + public @NonNull byte[] read(@NonNull final String owner, @NonNull final String source, + @NonNull final Long offset, @NonNull final Integer limit) throws Exception + { return new byte[0]; } @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 { + final byte @NonNull [] md5Hash) throws Exception + { return null; } @Override public @NonNull Boolean delete(@NonNull final String owner, @NonNull final String source) - throws Exception { + throws Exception + { return null; } @Override - public @NonNull Boolean copy(@NonNull final String sourceOwner, @NonNull final String source, @NonNull final String targetOwner, + public @NonNull Boolean copy(@NonNull final String sourceOwner, @NonNull final String source, + @NonNull final String targetOwner, @NonNull final String target) throws Exception { @@ -119,7 +148,8 @@ public final class AfsClient implements PublicAPI } @Override - public @NonNull Boolean move(@NonNull final String sourceOwner, @NonNull final String source, @NonNull final String targetOwner, + public @NonNull Boolean move(@NonNull final String sourceOwner, @NonNull final String source, + @NonNull final String targetOwner, @NonNull final String target) throws Exception { @@ -127,53 +157,65 @@ public final class AfsClient implements PublicAPI } @Override - public void begin(final UUID transactionId) throws Exception { + public void begin(final UUID transactionId) throws Exception + { } @Override - public Boolean prepare() throws Exception { + public Boolean prepare() throws Exception + { return null; } @Override - public void commit() throws Exception { + public void commit() throws Exception + { } @Override - public void rollback() throws Exception { + public void rollback() throws Exception + { } @Override - public List<UUID> recover() throws Exception { + public List<UUID> recover() throws Exception + { return null; } private <T> T request(@NonNull final String httpMethod, @NonNull final String apiMethod, - @NonNull final Map<String, String> parameters) throws Exception { + @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) throws Exception { - HttpClient client = HttpClient.newBuilder() + @NonNull final Map<String, String> parameters, final byte @NonNull [] body) + throws Exception + { + HttpClient.Builder clientBuilder = HttpClient.newBuilder() .version(HttpClient.Version.HTTP_1_1) .followRedirects(HttpClient.Redirect.NORMAL) - .connectTimeout(Duration.ofMillis(timeout)) - .build(); + .connectTimeout(Duration.ofMillis(timeout)); + Map<String, String> params = parameters; + + HttpClient client = clientBuilder.build(); - final String query = Stream.concat(Stream.of(new AbstractMap.SimpleImmutableEntry<>("method", apiMethod)), - parameters.entrySet().stream()) + final String query = Stream.concat( + Stream.of(new AbstractMap.SimpleImmutableEntry<>("method", apiMethod)), + params.entrySet().stream()) .map(entry -> urlEncode(entry.getKey()) + "=" + urlEncode(entry.getValue())) .reduce((s1, s2) -> s1 + "&" + s2).get(); - final URI uri = new URI(serverUri.getScheme(), null, serverUri.getHost(), serverUri.getPort(), - serverUri.getPath(), query, null); + final URI uri = + new URI(serverUri.getScheme(), null, serverUri.getHost(), serverUri.getPort(), + serverUri.getPath(), query, null); - final HttpRequest.Builder builder = HttpRequest.newBuilder() + HttpRequest.Builder builder = HttpRequest.newBuilder() .uri(uri) .version(HttpClient.Version.HTTP_1_1) .timeout(Duration.ofMillis(timeout)) @@ -181,23 +223,31 @@ public final class AfsClient implements PublicAPI final HttpRequest request = builder.build(); - final HttpResponse<byte[]> httpResponse = client.send(request, HttpResponse.BodyHandlers.ofByteArray()); + final HttpResponse<byte[]> httpResponse = + client.send(request, HttpResponse.BodyHandlers.ofByteArray()); final int statusCode = httpResponse.statusCode(); - if (statusCode >= 200 && statusCode < 300) { - final ApiResponse response = jsonObjectMapper.readValue(new ByteArrayInputStream(httpResponse.body()), - ApiResponse.class); - - if (response.getError() != null) { + if (statusCode >= 200 && statusCode < 300) + { + final ApiResponse response = + jsonObjectMapper.readValue(new ByteArrayInputStream(httpResponse.body()), + ApiResponse.class); + + if (response.getError() != null) + { throw ClientExceptions.API_ERROR.getInstance(response.getError()); - } else { + } else + { return (T) response.getResult(); } - } else if (statusCode >= 400 && statusCode < 500) { + } else if (statusCode >= 400 && statusCode < 500) + { throw ClientExceptions.CLIENT_ERROR.getInstance(statusCode); - } else if (statusCode >= 500 && statusCode < 600) { + } else if (statusCode >= 500 && statusCode < 600) + { throw ClientExceptions.SERVER_ERROR.getInstance(statusCode); - } else { + } else + { throw ClientExceptions.OTHER_ERROR.getInstance(statusCode); } } diff --git a/api-data-store-server-java/src/test/java/ch/ethz/sis/afsclient/client/AfsClientTest.java b/api-data-store-server-java/src/test/java/ch/ethz/sis/afsclient/client/AfsClientTest.java index b4ada3b7708bf88e59347c0d389a16f58ced0b9d..a9ec352489faa11d83b5bfd371c1fbc6972a4cf7 100644 --- a/api-data-store-server-java/src/test/java/ch/ethz/sis/afsclient/client/AfsClientTest.java +++ b/api-data-store-server-java/src/test/java/ch/ethz/sis/afsclient/client/AfsClientTest.java @@ -1,81 +1,117 @@ package ch.ethz.sis.afsclient.client; import static org.junit.Assert.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.CoreMatchers.containsString; import java.net.URI; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.*; public class AfsClientTest { private static DummyHttpServer httpServer; - private static AfsClient afsClient; + private AfsClient afsClient; + private static final int HTTP_SERVER_PORT = 8085; + private static final String HTTP_SERVER_PATH = "/fileserver"; - private static int httpServerPort; - - private static String httpServerPath; - - @BeforeClass - public static void classSetUp() throws Exception - { - httpServerPort = 8085; - httpServerPath = "/fileserver"; - httpServer = new DummyHttpServer(httpServerPort, httpServerPath); + @Before + public void setUp() throws Exception { + httpServer = new DummyHttpServer(HTTP_SERVER_PORT, HTTP_SERVER_PATH); httpServer.start(); afsClient = new AfsClient( - new URI("http", null, "localhost", httpServerPort, - httpServerPath, null, null)); + new URI("http", null, "localhost", HTTP_SERVER_PORT, + HTTP_SERVER_PATH, null, null)); } - @AfterClass - public static void classTearDown() throws Exception - { + @After + public void tearDown() { httpServer.stop(); } @Test - public void testLogin() throws Exception + public void login_methodIsPost() throws Exception { final String token = afsClient.login("test", "test"); assertNotNull(token); + assertEquals(token, afsClient.getSessionToken()); + assertEquals("POST", httpServer.getHttpExchange().getRequestMethod()); + } + + @Test + public void isSessionValid_withoutLogin_throwsException() throws Exception + { + try + { + afsClient.isSessionValid(); + fail(); + } catch (IllegalStateException e) { + assertThat(e.getMessage(), containsString("No session information detected!")); + } } @Test - public void testIsSessionValid() + public void isSessionValid_afterLogin_methodIsGet() throws Exception { + afsClient.login("test", "test"); + httpServer.setNextResponse("{\"result\": true}"); + + Boolean result = afsClient.isSessionValid(); + + assertTrue(result); + assertEquals("GET", httpServer.getHttpExchange().getRequestMethod()); + } + + @Test + public void logout_withoutLogin_throwsException() throws Exception + { + try + { + afsClient.logout(); + fail(); + } catch (IllegalStateException e) { + assertThat(e.getMessage(), containsString("No session information detected!")); + } } @Test - public void testLogout() + public void logout_sessionTokenIsCleared() throws Exception { + afsClient.login("test", "test"); + assertNotNull(afsClient.getSessionToken()); + + httpServer.setNextResponse("{\"result\": true}"); + + Boolean result = afsClient.logout(); + assertTrue(result); + assertNull(afsClient.getSessionToken()); + assertEquals("POST", httpServer.getHttpExchange().getRequestMethod()); } @Test - public void testList() + public void testList() throws Exception { } @Test - public void testRead() + public void testRead()throws Exception { } @Test - public void testWrite() + public void testWrite()throws Exception { } @Test - public void testDelete() + public void testDelete()throws Exception { } @Test - public void testCopy() + public void testCopy()throws Exception { } diff --git a/api-data-store-server-java/src/test/java/ch/ethz/sis/afsclient/client/DummyHttpServer.java b/api-data-store-server-java/src/test/java/ch/ethz/sis/afsclient/client/DummyHttpServer.java index 2450273d9a0294a39a995d3729b8128c0aa02f8a..f4d9337fce7294bc4a63ceaa945978785a6914bf 100644 --- a/api-data-store-server-java/src/test/java/ch/ethz/sis/afsclient/client/DummyHttpServer.java +++ b/api-data-store-server-java/src/test/java/ch/ethz/sis/afsclient/client/DummyHttpServer.java @@ -36,6 +36,7 @@ public final class DummyHttpServer private static final String DEFAULT_RESPONSE = "{\"result\": \"success\"}"; private String nextResponse = DEFAULT_RESPONSE; + private HttpExchange httpExchange; public DummyHttpServer(int httpServerPort, String httpServerPath) throws IOException { @@ -50,6 +51,7 @@ public final class DummyHttpServer exchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, response.length); exchange.getResponseBody().write(response); exchange.close(); + httpExchange = exchange; } }); } @@ -69,4 +71,8 @@ public final class DummyHttpServer this.nextResponse = response; } + public HttpExchange getHttpExchange() { + return httpExchange; + } + } diff --git a/server-data-store/build.gradle b/server-data-store/build.gradle index 35e6698c5b46c0f74744774657cc99010f6c3c92..b468482a87654872625e93d67492554f679e1fef 100644 --- a/server-data-store/build.gradle +++ b/server-data-store/build.gradle @@ -26,8 +26,8 @@ dependencies { 'log4j:log4j-api:2.10.0', 'log4j:log4j-core:2.10.0', 'openbis:openbis-v3-api-batteries-included:20.10.5'; - testImplementation 'junit:junit:4.10' - testRuntimeOnly 'hamcrest:hamcrest-core:1.3' + testImplementation 'junit:junit:4.10', + 'hamcrest:hamcrest-core:1.3' } task AFSServerDevelopmentEnvironmentStart(type: JavaExec) { 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 b9efb2ba185fd2e3dedbfe9d0b639cb0cb9b3241..50099d69e6814d9989fbe65405320b8403ef096c 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 @@ -26,75 +26,96 @@ import io.netty.channel.ChannelHandlerContext; 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.*; -public class NettyHttpHandler extends ChannelInboundHandlerAdapter { +public class NettyHttpHandler 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 NettyHttpHandler(String uri, HttpServerHandler httpServerHandler) { + public NettyHttpHandler(String uri, HttpServerHandler httpServerHandler) + { this.uri = uri; this.httpServerHandler = httpServerHandler; } @Override - public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { - if (msg instanceof FullHttpRequest) { + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception + { + if (msg instanceof FullHttpRequest) + { final FullHttpRequest request = (FullHttpRequest) msg; QueryStringDecoder queryStringDecoder = new QueryStringDecoder(request.uri(), true); if (queryStringDecoder.path().equals(uri) && - allowedMethods.contains(request.method())) { + allowedMethods.contains(request.method())) + { FullHttpResponse response = null; ByteBuf content = request.content(); - try { - byte[] contentAsArray = (content.hasArray())?content.array():null; - HttpResponse apiResponse = httpServerHandler.process(request.method(), queryStringDecoder.parameters(), contentAsArray); - HttpResponseStatus status = (!apiResponse.isError())?HttpResponseStatus.OK:HttpResponseStatus.BAD_REQUEST; + try + { + byte[] array = new byte[content.readableBytes()]; + content.readBytes(array); + HttpResponse apiResponse = httpServerHandler.process(request.method(), + queryStringDecoder.parameters(), array); + 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 { + } finally + { content.release(); } - } else { + } else + { FullHttpResponse response = getHttpResponse( - HttpResponseStatus.NOT_FOUND, - "text/plain", - NOT_FOUND_BUFFER, - NOT_FOUND.length); + HttpResponseStatus.NOT_FOUND, + "text/plain", + NOT_FOUND_BUFFER, + NOT_FOUND.length); ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } - } else { + } else + { super.channelRead(ctx, msg); } } @Override - public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { + public void channelReadComplete(ChannelHandlerContext ctx) throws Exception + { ctx.flush(); } @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + 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 - ); + HttpResponseStatus.INTERNAL_SERVER_ERROR, + "text/plain", + Unpooled.wrappedBuffer(causeBytes), + causeBytes.length + ); ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } @@ -102,7 +123,8 @@ public class NettyHttpHandler extends ChannelInboundHandlerAdapter { HttpResponseStatus status, String contentType, ByteBuf content, - int contentLength) { + int contentLength) + { FullHttpResponse response = new DefaultFullHttpResponse( HttpVersion.HTTP_1_1, status, diff --git a/server-data-store/src/main/java/ch/ethz/sis/afsserver/http/impl/NettyHttpServer.java b/server-data-store/src/main/java/ch/ethz/sis/afsserver/http/impl/NettyHttpServer.java index b96132285bfa51c82b2207b5bd0dc01713960c8e..dbfa4dc78b0c0e82dc6094df920116f6afc0c58b 100644 --- a/server-data-store/src/main/java/ch/ethz/sis/afsserver/http/impl/NettyHttpServer.java +++ b/server-data-store/src/main/java/ch/ethz/sis/afsserver/http/impl/NettyHttpServer.java @@ -31,77 +31,101 @@ import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpServerCodec; import io.netty.util.concurrent.Future; -public class NettyHttpServer implements HttpServer { +public class NettyHttpServer implements HttpServer +{ private static final Logger logger = LogManager.getLogger(NettyHttpServer.class); private final EventLoopGroup masterGroup; + private final EventLoopGroup slaveGroup; private ChannelFuture channel; - public NettyHttpServer() { + public NettyHttpServer() + { masterGroup = new NioEventLoopGroup(); slaveGroup = new NioEventLoopGroup(); } - public void start(int port, int maxContentLength, String uri, HttpServerHandler httpServerHandler) { + public void start(int port, int maxContentLength, String uri, + HttpServerHandler httpServerHandler) + { Integer maxQueueLengthForIncomingConnections = 128; - Runtime.getRuntime().addShutdownHook(new Thread() { + Runtime.getRuntime().addShutdownHook(new Thread() + { @Override - public void run() { + public void run() + { shutdown(true); } }); - try { + try + { final ServerBootstrap bootstrap = new ServerBootstrap() .group(masterGroup, slaveGroup) .channel(NioServerSocketChannel.class) - .childHandler(new ChannelInitializer<SocketChannel>() { + .childHandler(new ChannelInitializer<SocketChannel>() + { @Override - public void initChannel(final SocketChannel ch) throws Exception { + public void initChannel(final SocketChannel ch) throws Exception + { ch.pipeline().addLast("codec", new HttpServerCodec()); - ch.pipeline().addLast("aggregator", new HttpObjectAggregator(maxContentLength)); - ch.pipeline().addLast("request", new NettyHttpHandler(uri, httpServerHandler)); + ch.pipeline().addLast("aggregator", + new HttpObjectAggregator(maxContentLength)); + ch.pipeline().addLast("request", + new NettyHttpHandler(uri, httpServerHandler)); } }) .option(ChannelOption.SO_BACKLOG, maxQueueLengthForIncomingConnections) .option(ChannelOption.SO_REUSEADDR, Boolean.TRUE) .childOption(ChannelOption.SO_KEEPALIVE, Boolean.TRUE); channel = bootstrap.bind(port).sync(); - } catch (final Exception ex) { + } catch (final Exception ex) + { logger.catching(ex); } } - public void shutdown(boolean gracefully) { - try { + public void shutdown(boolean gracefully) + { + try + { channel.channel().close(); - } catch (Exception ex) { + } catch (Exception ex) + { logger.catching(ex); } - try { - if (gracefully) { + try + { + if (gracefully) + { Future slaveShutdown = slaveGroup.shutdownGracefully(); slaveShutdown.await(); - } else { + } else + { slaveGroup.shutdown(); } - } catch (Exception ex) { + } catch (Exception ex) + { logger.catching(ex); } - try { - if (gracefully) { + try + { + if (gracefully) + { Future masterShutdown = masterGroup.shutdownGracefully(); masterShutdown.await(); - } else { + } else + { masterGroup.shutdown(); } - } catch (Exception ex) { + } catch (Exception ex) + { logger.catching(ex); } } 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 ebaf263978d449ea4983d1f622f37889ab83cd29..3a8f9991c53b9ca39bcf5eaad8b20dc006e8e594 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,6 +15,8 @@ */ package ch.ethz.sis.afsserver.server.impl; +import static java.nio.charset.StandardCharsets.UTF_8; + import ch.ethz.sis.afsserver.exception.HTTPExceptions; import ch.ethz.sis.afsserver.http.*; import ch.ethz.sis.afsserver.server.*; @@ -31,50 +33,56 @@ import java.util.*; /* * This class is supposed to be called by a TCP or HTTP transport class */ -public class ApiServerAdapter<CONNECTION, API> implements HttpServerHandler { +public class ApiServerAdapter<CONNECTION, API> implements HttpServerHandler +{ private static final Logger logger = LogManager.getLogger(ApiServerAdapter.class); private final APIServer<CONNECTION, Request, Response, API> server; + private final JsonObjectMapper jsonObjectMapper; - private final ApiResponseBuilder apiResponseBuilder; + private final ApiResponseBuilder apiResponseBuilder; public ApiServerAdapter( APIServer<CONNECTION, Request, Response, API> server, - JsonObjectMapper jsonObjectMapper) { + JsonObjectMapper jsonObjectMapper) + { this.server = server; this.jsonObjectMapper = jsonObjectMapper; this.apiResponseBuilder = new ApiResponseBuilder(); } - public static HttpMethod getHttpMethod(String apiMethod) { - HttpMethod httpMethod = null; - switch (apiMethod){ - case "delete": - httpMethod = HttpMethod.DELETE; - break; - case "write": - httpMethod = HttpMethod.PUT; - break; + public static HttpMethod getHttpMethod(String apiMethod) + { + switch (apiMethod) + { case "list": case "read": case "isSessionValid": - httpMethod = HttpMethod.GET; - break; - default: - httpMethod = HttpMethod.POST; + return HttpMethod.GET; // all parameters from GET methods come on the query string + case "write": + case "move": + case "login": + case "logout": + return HttpMethod.POST; // all parameters from POST methods come on the body + case "delete": + return HttpMethod.DELETE; // all parameters from DELETE methods come on the body } - return httpMethod; + throw new UnsupportedOperationException("This line SHOULD NOT be unreachable!"); } - public boolean isValidMethod(HttpMethod givenMethod, String apiMethod) { + public boolean isValidMethod(HttpMethod givenMethod, String apiMethod) + { HttpMethod correctMethod = getHttpMethod(apiMethod); return correctMethod == givenMethod; } - public HttpResponse process(HttpMethod httpMethod, Map<String, List<String>> uriParameters, byte[] requestBody) { - try { + public HttpResponse process(HttpMethod httpMethod, Map<String, List<String>> uriParameters, + byte[] requestBody) + { + try + { logger.traceAccess(null); PerformanceAuditor performanceAuditor = new PerformanceAuditor(); @@ -83,22 +91,31 @@ public class ApiServerAdapter<CONNECTION, API> implements HttpServerHandler { String interactiveSessionKey = null; String transactionManagerKey = null; Map<String, Object> methodParameters = new HashMap<>(); - for (Map.Entry<String, List<String>> entry:uriParameters.entrySet()) { + for (Map.Entry<String, List<String>> entry : uriParameters.entrySet()) + { String value = null; - if (entry.getValue() != null) { - if (entry.getValue().size() == 1) { + 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())); + } else if (entry.getValue().size() > 1) + { + return getHTTPResponse(new ApiResponse("1", null, + HTTPExceptions.INVALID_PARAMETERS.getCause())); } } - try { - switch (entry.getKey()) { + try + { + switch (entry.getKey()) + { case "method": method = value; - if (!isValidMethod(httpMethod, method)) { - return getHTTPResponse(new ApiResponse("1", null, HTTPExceptions.INVALID_HTTP_METHOD.getCause())); + if (!isValidMethod(httpMethod, method)) + { + return getHTTPResponse(new ApiResponse("1", null, + HTTPExceptions.INVALID_HTTP_METHOD.getCause())); } break; case "sessionToken": @@ -129,54 +146,80 @@ public class ApiServerAdapter<CONNECTION, API> implements HttpServerHandler { methodParameters.put(entry.getKey(), value); break; } - } catch (Exception e) { + } catch (Exception e) + { logger.catching(e); - return getHTTPResponse(new ApiResponse("1", null, HTTPExceptions.INVALID_PARAMETERS.getCause(e.getClass().getSimpleName(), e.getMessage()))); + return getHTTPResponse(new ApiResponse("1", null, + HTTPExceptions.INVALID_PARAMETERS.getCause(e.getClass().getSimpleName(), + e.getMessage()))); } } - if (method.equals("write")) { - methodParameters.put("data", requestBody); + switch (method) { + case "write": + methodParameters.put("data", requestBody); + break; + case "login": + // userId : password + String[] credentials = new String(requestBody, UTF_8).split(":"); + methodParameters.put("userId", credentials[0]); + methodParameters.put("password", credentials[1]); + break; } - ApiRequest apiRequest = new ApiRequest("1", method, methodParameters, sessionToken, interactiveSessionKey, transactionManagerKey); - Response response = server.processOperation(apiRequest, apiResponseBuilder, performanceAuditor); + + 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) { + } catch (APIServerException e) + { logger.catching(e); - switch (e.getType()) { + switch (e.getType()) + { case MethodNotFound: case IncorrectParameters: case InternalError: - try { + try + { return getHTTPResponse(new ApiResponse("1", null, e.getData())); - } catch (Exception ex) { + } catch (Exception ex) + { logger.catching(ex); } } - } catch (Exception e) { + } catch (Exception e) + { logger.catching(e); - try { - return getHTTPResponse(new ApiResponse("1", null, HTTPExceptions.UNKNOWN.getCause(e.getClass().getSimpleName(), e.getMessage()))); - } catch (Exception ex) { + 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. } - private HttpResponse getHTTPResponse(Response response) throws Exception { + private HttpResponse getHTTPResponse(Response response) throws Exception + { boolean error = response.getError() != null; String contentType = null; byte[] body = null; - if (response.getResult() instanceof byte[]) { + if (response.getResult() instanceof byte[]) + { contentType = HttpResponse.CONTENT_TYPE_BINARY_DATA; body = (byte[]) response.getResult(); - } else { + } else + { contentType = HttpResponse.CONTENT_TYPE_JSON; body = jsonObjectMapper.writeValue(response); } diff --git a/server-data-store/src/test/java/ch/ethz/sis/afsserver/ApiClientTest.java b/server-data-store/src/test/java/ch/ethz/sis/afsserver/ApiClientTest.java index d443be045773553d6f5bfb309d388fb2cb5d680c..e9065b602411801a27fb50c38a61efa807e413d2 100644 --- a/server-data-store/src/test/java/ch/ethz/sis/afsserver/ApiClientTest.java +++ b/server-data-store/src/test/java/ch/ethz/sis/afsserver/ApiClientTest.java @@ -17,14 +17,14 @@ package ch.ethz.sis.afsserver; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.CoreMatchers.containsString; import static org.junit.Assert.*; import java.net.URI; import java.util.List; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.*; import ch.ethz.sis.afs.manager.TransactionConnection; import ch.ethz.sis.afsclient.client.AfsClient; @@ -39,6 +39,10 @@ public final class ApiClientTest private static AfsClient afsClient; + private static int httpServerPort; + + private static String httpServerPath; + @BeforeClass public static void classSetUp() throws Exception { @@ -47,16 +51,25 @@ public final class ApiClientTest "src/test/resources/test-server-config.properties"); final DummyServerObserver dummyServerObserver = new DummyServerObserver(); afsServer = new Server<>(configuration, dummyServerObserver, dummyServerObserver); - - final int httpServerPort = + httpServerPort = configuration.getIntegerProperty(AtomicFileSystemServerParameter.httpServerPort); - final String httpServerPath = + httpServerPath = configuration.getStringProperty(AtomicFileSystemServerParameter.httpServerUri); + } + + @Before + public void setUp() throws Exception + { afsClient = new AfsClient( new URI("http", null, "localhost", httpServerPort, httpServerPath, null, null)); } + private String login() throws Exception + { + return afsClient.login("test", "test"); + } + @AfterClass public static void classTearDown() throws Exception { @@ -64,10 +77,55 @@ public final class ApiClientTest } @Test - public void testLogin() throws Exception + public void login_sessionTokenIsNotNull() throws Exception { - final String token = afsClient.login("test", "test"); + final String token = login(); assertNotNull(token); } + @Test + public void isSessionValid_throwsException() throws Exception + { + try + { + afsClient.isSessionValid(); + fail(); + } catch (IllegalStateException e) + { + assertThat(e.getMessage(), containsString("No session information detected!")); + } + } + + @Test + public void isSessionValid_returnsTrue() throws Exception + { + login(); + + final Boolean isValid = afsClient.isSessionValid(); + assertTrue(isValid); + } + + @Test + public void logout_withoutLogin_throwsException() throws Exception + { + try + { + afsClient.logout(); + fail(); + } catch (IllegalStateException e) + { + assertThat(e.getMessage(), containsString("No session information detected!")); + } + } + + @Test + public void logout_withLogin_returnsTrue() throws Exception + { + login(); + + final Boolean result = afsClient.logout(); + + assertTrue(result); + } + }