/* * Copyright (C) 2017-2020 HERE Europe B.V. * * 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. * * SPDX-License-Identifier: Apache-2.0 * License-Filename: LICENSE */ package com.here.xyz.hub.rest; import static com.here.xyz.hub.rest.Api.HeaderValues.APPLICATION_JSON; import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST; import static io.vertx.core.http.HttpHeaders.ACCEPT; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.here.xyz.hub.rest.ApiParam.Path; import com.here.xyz.hub.rest.ApiParam.Query; import com.here.xyz.hub.task.FeatureTaskHandler.InvalidStorageException; import com.here.xyz.hub.task.ModifyOp.IfExists; import com.here.xyz.hub.task.ModifyOp.IfNotExists; import com.here.xyz.hub.task.ModifySpaceOp; import com.here.xyz.hub.task.SpaceTask.ConditionalOperation; import com.here.xyz.hub.task.SpaceTask.MatrixReadQuery; import com.here.xyz.hub.task.Task; import com.here.xyz.models.hub.Space.Copyright; import com.here.xyz.responses.ErrorResponse; import io.vertx.core.json.DecodeException; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.RoutingContext; import io.vertx.ext.web.api.contract.openapi3.OpenAPI3RouterFactory; import java.util.Collections; import java.util.List; import java.util.Map; public class SpaceApi extends Api { public SpaceApi(OpenAPI3RouterFactory routerFactory) { routerFactory.addHandlerByOperationId("getSpace", this::getSpace); routerFactory.addHandlerByOperationId("getSpaces", this::getSpaces); routerFactory.addHandlerByOperationId("postSpace", this::postSpace); routerFactory.addHandlerByOperationId("patchSpace", this::patchSpace); routerFactory.addHandlerByOperationId("deleteSpace", this::deleteSpace); } /** * Read a space. */ public void getSpace(final RoutingContext context) { Map<String, Object> input = new JsonObject().put("id", context.pathParam(ApiParam.Path.SPACE_ID)).getMap(); ModifySpaceOp modifyOp = new ModifySpaceOp(Collections.singletonList(input), IfNotExists.ERROR, IfExists.RETAIN, true); new ConditionalOperation(context, ApiResponseType.SPACE, modifyOp, true) .execute(this::sendResponse, this::sendErrorResponse); } /** * List all spaces accessible for the provided credentials. */ public void getSpaces(final RoutingContext context) { new MatrixReadQuery( context, ApiResponseType.SPACE_LIST, ApiParam.Query.getBoolean(context, ApiParam.Query.INCLUDE_RIGHTS, false), ApiParam.Query.getBoolean(context, Query.INCLUDE_CONNECTORS, false), ApiParam.Query.getString(context, ApiParam.Query.OWNER, MatrixReadQuery.ME) ).execute(this::sendResponse, this::sendErrorResponse); } /** * Create a new space. */ public void postSpace(final RoutingContext context) { JsonObject input; try { input = context.getBodyAsJson(); } catch (DecodeException e) { context.fail(new HttpException(BAD_REQUEST, "Invalid JSON string")); return; } ModifySpaceOp modifyOp = new ModifySpaceOp(Collections.singletonList(input.getMap()), IfNotExists.CREATE, IfExists.ERROR, true); new ConditionalOperation(context, ApiResponseType.SPACE, modifyOp, false) .execute(this::sendResponse, this::sendErrorResponseOnEdit); } /** * Update a space. */ public void patchSpace(final RoutingContext context) { JsonObject input; try { input = context.getBodyAsJson(); } catch (DecodeException e) { context.fail(new HttpException(BAD_REQUEST, "Invalid JSON string")); return; } String pathId = context.pathParam(Path.SPACE_ID); if (input.getString("id") == null) { input.put("id", pathId); } if (!input.getString("id").equals(pathId)) { context.fail( new HttpException(BAD_REQUEST, "The space ID in the body does not match the ID in the resource path.")); return; } ModifySpaceOp modifyOp = new ModifySpaceOp(Collections.singletonList(input.getMap()), IfNotExists.ERROR, IfExists.PATCH, true); new ConditionalOperation(context, ApiResponseType.SPACE, modifyOp, true) .execute(this::sendResponse, this::sendErrorResponseOnEdit); } /** * Delete a space. */ public void deleteSpace(final RoutingContext context) { Map<String,Object> input = new JsonObject().put("id", context.pathParam(Path.SPACE_ID)).getMap(); ModifySpaceOp modifyOp = new ModifySpaceOp(Collections.singletonList(input), IfNotExists.ERROR, IfExists.DELETE, true); //Delete the space ApiResponseType responseType = APPLICATION_JSON.equals(context.request().getHeader(ACCEPT)) ? ApiResponseType.SPACE : ApiResponseType.EMPTY; new ConditionalOperation(context, responseType, modifyOp, true) .execute(this::sendResponse, this::sendErrorResponse); } /** * Send an error response to the client when an exception occurred while processing a task. * * @param task the task for which to return an error response. * @param e the exception that should be used to generate an {@link ErrorResponse}, if null an internal server error is returned. */ public void sendErrorResponseOnEdit(final Task task, final Exception e) { if (e instanceof InvalidStorageException) { sendErrorResponse(task.context, new HttpException(BAD_REQUEST, "The space contains an invalid storage ID.")); } else { sendErrorResponse(task.context, e); } } @JsonIgnoreProperties(ignoreUnknown = true) public static class BasicSpaceView { public String id; public String owner; public String title; public String description; @JsonInclude(Include.NON_DEFAULT) public List<Copyright> copyright; } @JsonIgnoreProperties(ignoreUnknown = true) public static class RightsSpaceView extends BasicSpaceView { public List<String> rights; } }