package org.rakam.ui.report; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; 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 io.netty.handler.codec.http.HttpVersion; import org.rakam.server.http.HttpService; import org.rakam.server.http.RakamHttpRequest; import org.rakam.server.http.annotations.Api; import org.rakam.server.http.annotations.ApiOperation; import org.rakam.server.http.annotations.Authorization; import org.rakam.server.http.annotations.HeaderParam; import org.rakam.server.http.annotations.IgnoreApi; import org.rakam.ui.ProtectEndpoint; import org.rakam.ui.UIPermissionParameterProvider.Project; 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 java.lang.Boolean.TRUE; import static org.rakam.server.http.HttpServer.returnError; @Path("/ui/recipe") @IgnoreApi @Api(value = "/ui/recipe", nickname = "recipe", description = "Recipe operations", tags = "recipe") public class UIRecipeHttpService extends HttpService { private static ObjectMapper yamlMapper; static { yamlMapper = new ObjectMapper(new YAMLFactory()); yamlMapper.registerModule(new JSR310Module()); yamlMapper.registerModule(new Jdk8Module()); } private final UIRecipeHandler installer; @Inject public UIRecipeHttpService(UIRecipeHandler installer) { this.installer = installer; } @ApiOperation(value = "Install recipe", response = SuccessMessage.class) @POST @ProtectEndpoint(writeOperation = true) @Path("/install") public void installUIRecipe(RakamHttpRequest request, @Named("user_id") Project project) { String contentType = request.headers().get(CONTENT_TYPE); ExportType exportType = Arrays.stream(ExportType.values()) .filter(f -> f.contentType.equals(contentType)) .findAny() .orElse(ExportType.YAML); boolean override = ImmutableList.of(TRUE.toString()).equals(request.params().get("override")); request.bodyHandler(body -> { UIRecipe recipe; try { recipe = exportType.mapper.readValue(body, UIRecipe.class); } catch (IOException e) { returnError(request, e.getMessage(), HttpResponseStatus.BAD_REQUEST); return; } try { installer.install(recipe, project.userId, project.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 = UIRecipe.class, authorizations = @Authorization(value = "master_key") ) @GET @Path("/export") public void exportUIRecipe(@HeaderParam("Accept") String contentType, @Named("user_id") Project project, RakamHttpRequest request) throws JsonProcessingException { request.bodyHandler(s -> { UIRecipe export = installer.export(project.userId, project.project); ExportType exportType = Arrays.stream(ExportType.values()) .filter(f -> f.contentType.equals(contentType)) .findAny() .orElse(ExportType.YAML); ByteBuf buffer; try { buffer = Unpooled.wrappedBuffer(exportType.mapper.writeValueAsBytes(export)); } catch (JsonProcessingException e) { throw Throwables.propagate(e); } DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.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()); } } }