package org.rakam.recipe; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.Version; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; import com.fasterxml.jackson.datatype.jsr310.JSR310Module; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.inject.Inject; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.HttpResponseStatus; import org.rakam.analysis.ApiKeyService; import org.rakam.server.http.HttpService; import org.rakam.server.http.RakamHttpRequest; import org.rakam.server.http.SwaggerJacksonAnnotationIntrospector; import org.rakam.server.http.annotations.Api; import org.rakam.server.http.annotations.ApiOperation; import org.rakam.server.http.annotations.ApiParam; import org.rakam.server.http.annotations.Authorization; import org.rakam.server.http.annotations.HeaderParam; import org.rakam.util.JsonHelper; import org.rakam.util.SuccessMessage; import javax.inject.Named; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import java.io.IOException; import java.util.Arrays; import static io.netty.handler.codec.http.HttpHeaders.Names.ACCESS_CONTROL_ALLOW_ORIGIN; import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE; import static io.netty.handler.codec.http.HttpHeaders.Names.ORIGIN; import static io.netty.handler.codec.http.HttpResponseStatus.OK; import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; import static org.rakam.analysis.ApiKeyService.AccessKeyType.MASTER_KEY; import static org.rakam.recipe.RecipeHttpService.ExportType.YAML; import static org.rakam.server.http.HttpServer.returnError; @Path("/recipe") @Api(value = "/recipe", nickname = "recipe", description = "Recipe operations", tags = "recipe") public class RecipeHttpService extends HttpService { private static ObjectMapper yamlMapper; private final ApiKeyService apiKeyService; static { yamlMapper = new ObjectMapper(new YAMLFactory()); yamlMapper.registerModule(new JSR310Module()); yamlMapper.registerModule(new Jdk8Module()); SwaggerJacksonAnnotationIntrospector ai = new SwaggerJacksonAnnotationIntrospector(); yamlMapper.registerModule( new SimpleModule("swagger", Version.unknownVersion()) { @Override public void setupModule(SetupContext context) { context.insertAnnotationIntrospector(ai); } }); } private final RecipeHandler installer; @Inject public RecipeHttpService(RecipeHandler installer, ApiKeyService apiKeyService) { this.installer = installer; this.apiKeyService = apiKeyService; } @ApiOperation(value = "Install recipe", authorizations = @Authorization(value = "master_key"), response = SuccessMessage.class ) @POST @Path("/install") public void installRecipe(RakamHttpRequest request) { String contentType = request.headers().get(CONTENT_TYPE); ExportType exportType = Arrays.stream(ExportType.values()) .filter(f -> f.contentType.equals(contentType)) .findAny() .orElse(YAML); boolean override = ImmutableList.of(Boolean.TRUE.toString()) .equals(request.params().get("override")); request.bodyHandler(body -> { Recipe recipe; try { recipe = exportType.mapper.readValue(body, Recipe.class); } catch (IOException e) { returnError(request, e.getMessage(), HttpResponseStatus.BAD_REQUEST); return; } String master_key = request.headers().get("master_key"); String project = apiKeyService.getProjectOfApiKey(master_key, MASTER_KEY); try { installer.install(recipe, project, override); request.response(JsonHelper.encode(SuccessMessage.success())).end(); } catch (Exception e) { returnError(request, "Error loading recipe: " + e.getMessage(), HttpResponseStatus.BAD_REQUEST); } }); } @ApiOperation(value = "Export recipe", response = Recipe.class, authorizations = @Authorization(value = "master_key") ) @GET @Path("/export") public void exportRecipe(@HeaderParam("Accept") String contentType, @Named("project") String project, RakamHttpRequest request) throws JsonProcessingException { request.bodyHandler(s -> { Recipe export = installer.export(project); ExportType exportType = Arrays.stream(ExportType.values()) .filter(f -> f.contentType.equals(contentType)) .findAny() .orElse(YAML); ByteBuf buffer; try { buffer = Unpooled.wrappedBuffer(exportType.mapper.writeValueAsBytes(export)); } catch (JsonProcessingException e) { throw Throwables.propagate(e); } DefaultFullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, buffer); response.headers().add(CONTENT_TYPE, exportType.contentType); if(request.headers().contains(ORIGIN)) { response.headers().set(ACCESS_CONTROL_ALLOW_ORIGIN, request.headers().get(ORIGIN)); } request.response(response).end(); }); } public enum ExportType { JSON(JsonHelper.getMapper(), "application/json"), YAML(yamlMapper, "application/x-yaml"); private final ObjectMapper mapper; private final String contentType; ExportType(ObjectMapper mapper, String contentType) { this.mapper = mapper; this.contentType = contentType; } @JsonCreator public static ExportType get(String name) { return valueOf(name.toUpperCase()); } } }