/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.workspace.server.stack;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import org.apache.commons.fileupload.FileItem;
import org.eclipse.che.api.core.ApiException;
import org.eclipse.che.api.core.BadRequestException;
import org.eclipse.che.api.core.ConflictException;
import org.eclipse.che.api.core.ForbiddenException;
import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.core.rest.Service;
import org.eclipse.che.api.core.rest.annotations.GenerateLink;
import org.eclipse.che.api.core.rest.shared.dto.Link;
import org.eclipse.che.api.core.util.LinksHelper;
import org.eclipse.che.api.workspace.server.model.impl.stack.StackImpl;
import org.eclipse.che.api.workspace.server.spi.StackDao;
import org.eclipse.che.api.workspace.server.stack.image.StackIcon;
import org.eclipse.che.api.workspace.shared.dto.stack.StackDto;
import org.eclipse.che.commons.env.EnvironmentContext;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
import static javax.ws.rs.core.MediaType.MULTIPART_FORM_DATA;
import static javax.ws.rs.core.MediaType.TEXT_PLAIN;
import static javax.ws.rs.core.Response.Status.CREATED;
import static org.eclipse.che.api.workspace.server.DtoConverter.asDto;
import static org.eclipse.che.api.workspace.shared.Constants.LINK_REL_CREATE_STACK;
import static org.eclipse.che.api.workspace.shared.Constants.LINK_REL_DELETE_ICON;
import static org.eclipse.che.api.workspace.shared.Constants.LINK_REL_GET_ICON;
import static org.eclipse.che.api.workspace.shared.Constants.LINK_REL_GET_STACK_BY_ID;
import static org.eclipse.che.api.workspace.shared.Constants.LINK_REL_REMOVE_STACK;
import static org.eclipse.che.api.workspace.shared.Constants.LINK_REL_SEARCH_STACKS;
import static org.eclipse.che.api.workspace.shared.Constants.LINK_REL_UPDATE_STACK;
import static org.eclipse.che.api.workspace.shared.Constants.LINK_REL_UPLOAD_ICON;
/**
* Defines Stack REST API
*
* @author Alexander Andrienko
*/
@Api(value = "/stack", description = "Stack REST API")
@Path("/stack")
public class StackService extends Service {
private final StackDao stackDao;
private final StackValidator stackValidator;
@Inject
public StackService(StackDao stackDao, StackValidator stackValidator) {
this.stackDao = stackDao;
this.stackValidator = stackValidator;
}
@POST
@Consumes(APPLICATION_JSON)
@Produces(APPLICATION_JSON)
@GenerateLink(rel = LINK_REL_CREATE_STACK)
@ApiOperation(value = "Create a new stack",
notes = "This operation can be performed only by authorized user",
response = StackDto.class)
@ApiResponses({@ApiResponse(code = 201, message = "The stack successfully created"),
@ApiResponse(code = 400, message = "Missed required parameters, parameters are not valid"),
@ApiResponse(code = 403, message = "The user does not have access to create a new stack"),
@ApiResponse(code = 409, message = "Conflict error occurred during the stack creation" +
"(e.g. The stack with such name already exists)"),
@ApiResponse(code = 500, message = "Internal server error occurred")})
public Response createStack(@ApiParam("The new stack") final StackDto stackDto) throws ApiException {
stackValidator.check(stackDto);
final String userId = EnvironmentContext.getCurrent().getSubject().getUserId();
final StackImpl newStack = StackImpl.builder()
.generateId()
.setName(stackDto.getName())
.setDescription(stackDto.getDescription())
.setScope(stackDto.getScope())
.setCreator(userId)
.setTags(stackDto.getTags())
.setWorkspaceConfig(stackDto.getWorkspaceConfig())
.setSource(stackDto.getSource())
.setComponents(stackDto.getComponents())
.build();
stackDao.create(newStack);
return Response.status(CREATED)
.entity(asStackDto(newStack))
.build();
}
@GET
@Path("/{id}")
@Produces(APPLICATION_JSON)
@GenerateLink(rel = LINK_REL_GET_STACK_BY_ID)
@ApiOperation(value = "Get the stack by id",
notes = "This operation can be performed for stack owner, or for predefined stacks")
@ApiResponses({@ApiResponse(code = 200, message = "The response contains requested stack entity"),
@ApiResponse(code = 404, message = "The requested stack was not found"),
@ApiResponse(code = 403, message = "The user has not permission get requested stack"),
@ApiResponse(code = 500, message = "Internal server error occurred")})
public StackDto getStack(@ApiParam("The stack id") @PathParam("id") final String id) throws ApiException {
return asStackDto(stackDao.getById(id));
}
@PUT
@Path("/{id}")
@Produces(APPLICATION_JSON)
@Consumes(APPLICATION_JSON)
@GenerateLink(rel = LINK_REL_UPDATE_STACK)
@ApiOperation(value = "Update the stack by replacing all the existing data (exclude field \"creator\") with update")
@ApiResponses({@ApiResponse(code = 200, message = "The stack successfully updated"),
@ApiResponse(code = 400, message = "Missed required parameters, parameters are not valid"),
@ApiResponse(code = 403, message = "The user does not have access to update the stack"),
@ApiResponse(code = 409, message = "Conflict error occurred during stack update" +
"(e.g. Stack with such name already exists)"),
@ApiResponse(code = 500, message = "Internal server error occurred")})
public StackDto updateStack(@ApiParam(value = "The stack update", required = true)
final StackDto updateDto,
@ApiParam(value = "The stack id", required = true)
@PathParam("id")
final String id) throws ApiException {
stackValidator.check(updateDto);
final StackImpl stack = stackDao.getById(id);
StackImpl stackForUpdate = StackImpl.builder()
.setId(id)
.setName(updateDto.getName())
.setDescription(updateDto.getDescription())
.setScope(updateDto.getScope())
//user can't edit creator
.setCreator(stack.getCreator())
.setTags(updateDto.getTags())
.setWorkspaceConfig(updateDto.getWorkspaceConfig())
.setSource(updateDto.getSource())
.setComponents(updateDto.getComponents())
.build();
return asStackDto(stackDao.update(stackForUpdate));
}
@DELETE
@Path("/{id}")
@GenerateLink(rel = LINK_REL_REMOVE_STACK)
@ApiOperation(value = "Removes the stack")
@ApiResponses({@ApiResponse(code = 204, message = "The stack successfully removed"),
@ApiResponse(code = 403, message = "The user does not have access to remove the stack"),
@ApiResponse(code = 404, message = "The stack doesn't exist"),
@ApiResponse(code = 500, message = "Internal server error occurred")})
public void removeStack(@ApiParam("The stack id") @PathParam("id") final String id) throws ApiException {
stackDao.remove(id);
}
@GET
@Produces(APPLICATION_JSON)
@GenerateLink(rel = LINK_REL_SEARCH_STACKS)
@ApiOperation(value = "Get the list stacks with required tags",
notes = "This operation can be performed only by authorized user",
response = StackDto.class,
responseContainer = "List")
@ApiResponses({@ApiResponse(code = 200, message = "The response contains requested list stack entity with required tags"),
@ApiResponse(code = 403, message = "The user does not have access to get stack entity list with required tags"),
@ApiResponse(code = 500, message = "Internal server error occurred")})
public List<StackDto> searchStacks(@ApiParam("List tags for search")
@QueryParam("tags")
final List<String> tags,
@ApiParam(value = "The number of the items to skip")
@DefaultValue("0")
@QueryParam("skipCount")
final Integer skipCount,
@ApiParam("The limit of the items in the response, default is 30")
@DefaultValue("30")
@QueryParam("maxItems")
final Integer maxItems) throws ServerException {
final String currentUser = EnvironmentContext.getCurrent().getSubject().getUserId();
return stackDao.searchStacks(currentUser, tags, skipCount, maxItems)
.stream()
.map(this::asStackDto)
.collect(Collectors.toList());
}
@GET
@Path("/{id}/icon")
@Produces("image/*")
@GenerateLink(rel = LINK_REL_GET_ICON)
@ApiOperation(value = "Get icon by stack id",
notes = "This operation can be performed only by authorized user",
response = byte[].class)
@ApiResponses({@ApiResponse(code = 200, message = "The response contains requested image entity"),
@ApiResponse(code = 403, message = "The user does not have access to get image entity"),
@ApiResponse(code = 500, message = "Internal server error occurred")})
public Response getIcon(@ApiParam("The stack id") @PathParam("id") final String id)
throws NotFoundException, ServerException, BadRequestException {
StackImpl stack = stackDao.getById(id);
if (stack == null) {
throw new NotFoundException("Stack with id '" + id + "' was not found.");
}
StackIcon image = stack.getStackIcon();
if (image == null) {
throw new NotFoundException("Image for stack with id '" + id + "' was not found.");
}
return Response.ok(image.getData(), image.getMediaType()).build();
}
@POST
@Path("/{id}/icon")
@Consumes(MULTIPART_FORM_DATA)
@Produces(TEXT_PLAIN)
@GenerateLink(rel = LINK_REL_UPLOAD_ICON)
@ApiOperation(value = "Upload icon for required stack",
notes = "This operation can be performed only by authorized stack owner")
@ApiResponses({@ApiResponse(code = 200, message = "Image was successfully uploaded"),
@ApiResponse(code = 400, message = "Missed required parameters, parameters are not valid"),
@ApiResponse(code = 403, message = "The user does not have access upload image for stack with required id"),
@ApiResponse(code = 404, message = "The stack doesn't exist"),
@ApiResponse(code = 500, message = "Internal server error occurred")})
public Response uploadIcon(@ApiParam("The image for stack")
final Iterator<FileItem> formData,
@ApiParam("The stack id")
@PathParam("id")
final String id)
throws NotFoundException, ServerException, BadRequestException, ForbiddenException, ConflictException {
if (formData.hasNext()) {
FileItem fileItem = formData.next();
StackIcon stackIcon = new StackIcon(fileItem.getName(), fileItem.getContentType(), fileItem.get());
StackImpl stack = stackDao.getById(id);
stack.setStackIcon(stackIcon);
stackDao.update(stack);
}
return Response.ok().build();
}
@DELETE
@Path("/{id}/icon")
@GenerateLink(rel = LINK_REL_DELETE_ICON)
@ApiOperation(value = "Delete icon for required stack",
notes = "This operation can be performed only by authorized stack owner")
@ApiResponses({@ApiResponse(code = 204, message = "Icon was successfully removed"),
@ApiResponse(code = 400, message = "Missed required parameters, parameters are not valid"),
@ApiResponse(code = 403, message = "The user does not have access upload image for stack with required id"),
@ApiResponse(code = 404, message = "The stack or icon doesn't exist"),
@ApiResponse(code = 409, message = "Conflict error occurred during stack update" +
"(e.g. Stack with such name already exists)"),
@ApiResponse(code = 500, message = "Internal server error occurred")})
public void removeIcon(@ApiParam("The stack Id") @PathParam("id") final String id)
throws NotFoundException, ServerException, ConflictException, ForbiddenException, BadRequestException {
StackImpl stack = stackDao.getById(id);
stack.setStackIcon(null);
stackDao.update(stack);
}
private StackDto asStackDto(StackImpl stack) {
final UriBuilder builder = getServiceContext().getServiceUriBuilder();
List<Link> links = new ArrayList<>();
final Link removeLink = LinksHelper.createLink("DELETE",
builder.clone()
.path(getClass(), "removeStack")
.build(stack.getId())
.toString(),
LINK_REL_REMOVE_STACK);
final Link getLink = LinksHelper.createLink("GET",
builder.clone()
.path(getClass(), "getStack")
.build(stack.getId())
.toString(),
APPLICATION_JSON,
LINK_REL_GET_STACK_BY_ID);
links.add(removeLink);
links.add(getLink);
StackIcon stackIcon = stack.getStackIcon();
if (stackIcon != null) {
Link deleteIcon = LinksHelper.createLink("DELETE",
builder.clone()
.path(getClass(), "removeIcon")
.build(stack.getId())
.toString(),
stackIcon.getMediaType(),
LINK_REL_DELETE_ICON);
Link getIconLink = LinksHelper.createLink("GET",
builder.clone()
.path(getClass(), "getIcon")
.build(stack.getId())
.toString(),
stackIcon.getMediaType(),
LINK_REL_GET_ICON);
links.add(deleteIcon);
links.add(getIconLink);
}
return asDto(stack).withLinks(links);
}
}