org.eclipse.che.api.machine.server.MachineService.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.che.api.machine.server.MachineService.java

Source

/*******************************************************************************
 * Copyright (c) 2012-2016 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.machine.server;

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 com.google.common.collect.Lists;
import com.google.common.io.CharStreams;

import org.eclipse.che.api.core.BadRequestException;
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.model.machine.Machine;
import org.eclipse.che.api.core.rest.Service;
import org.eclipse.che.api.core.rest.ServiceContext;
import org.eclipse.che.api.core.rest.shared.dto.Link;
import org.eclipse.che.api.core.rest.shared.dto.LinkParameter;
import org.eclipse.che.api.machine.server.exception.MachineException;
import org.eclipse.che.api.machine.server.impl.SnapshotImpl;
import org.eclipse.che.api.machine.server.spi.Instance;
import org.eclipse.che.api.machine.shared.Constants;
import org.eclipse.che.api.machine.shared.dto.CommandDto;
import org.eclipse.che.api.machine.shared.dto.MachineConfigDto;
import org.eclipse.che.api.machine.shared.dto.MachineDto;
import org.eclipse.che.api.machine.shared.dto.MachineProcessDto;
import org.eclipse.che.api.machine.shared.dto.NewSnapshotDescriptor;
import org.eclipse.che.api.machine.shared.dto.SnapshotDto;
import org.eclipse.che.commons.env.EnvironmentContext;

import javax.annotation.security.RolesAllowed;
import javax.inject.Inject;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.POST;
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.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriBuilder;
import java.io.IOException;
import java.io.Reader;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
import static javax.ws.rs.core.MediaType.TEXT_PLAIN;
import static org.eclipse.che.api.core.util.LinksHelper.createLink;
import static org.eclipse.che.dto.server.DtoFactory.cloneDto;
import static org.eclipse.che.dto.server.DtoFactory.newDto;

/**
 * Machine API
 *
 * @author Alexander Garagatyi
 * @author Anton Korneta
 */
@Api(value = "/machine", description = "Machine REST API")
@Path("/machine")
public class MachineService extends Service {
    private MachineManager machineManager;

    @Inject
    public MachineService(MachineManager machineManager) {
        this.machineManager = machineManager;
    }

    @GET
    @Path("/{machineId}")
    @Produces(MediaType.APPLICATION_JSON)
    @RolesAllowed("user")
    @ApiOperation(value = "Get machine by ID")
    @ApiResponses({ @ApiResponse(code = 200, message = "The response contains requested machine entity"),
            @ApiResponse(code = 404, message = "Machine with specified id does not exist"),
            @ApiResponse(code = 500, message = "Internal server error occurred") })
    public MachineDto getMachineById(@ApiParam(value = "Machine ID") @PathParam("machineId") String machineId)
            throws ServerException, ForbiddenException, NotFoundException {

        final Machine machine = machineManager.getMachine(machineId);

        checkCurrentUserPermissions(machine);

        return injectLinks(DtoConverter.asDto(machine));
    }

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @RolesAllowed("user")
    @ApiOperation(value = "Get all machines of workspace with specified ID", response = MachineDto.class, responseContainer = "List")
    @ApiResponses({ @ApiResponse(code = 200, message = "The response contains requested list of machine entities"),
            @ApiResponse(code = 400, message = "Workspace ID is not specified"),
            @ApiResponse(code = 500, message = "Internal server error occurred") })
    public List<MachineDto> getMachines(
            @ApiParam(value = "Workspace ID", required = true) @QueryParam("workspace") String workspaceId)
            throws ServerException, BadRequestException {

        requiredNotNull(workspaceId, "Parameter workspace");

        final String userId = EnvironmentContext.getCurrent().getUser().getId();

        return machineManager.getMachines(userId, workspaceId).stream().map(DtoConverter::asDto)
                .map(this::injectLinks).collect(Collectors.toList());
    }

    @DELETE
    @Path("/{machineId}")
    @Produces(MediaType.APPLICATION_JSON)
    @RolesAllowed("user")
    @ApiOperation(value = "Destroy machine")
    @ApiResponses({ @ApiResponse(code = 204, message = "Machine was successfully destroyed"),
            @ApiResponse(code = 404, message = "Machine with specified id does not exist"),
            @ApiResponse(code = 500, message = "Internal server error occurred") })
    public void destroyMachine(@ApiParam(value = "Machine ID") @PathParam("machineId") String machineId)
            throws NotFoundException, ServerException, ForbiddenException {

        checkCurrentUserPermissions(machineManager.getMachine(machineId));

        machineManager.destroy(machineId, true);
    }

    @GET
    @Path("/snapshot")
    @Produces(MediaType.APPLICATION_JSON)
    @RolesAllowed("user")
    @ApiOperation(value = "Get all snapshots of machines in workspace", response = SnapshotDto.class, responseContainer = "List")
    @ApiResponses({ @ApiResponse(code = 200, message = "The response contains requested list of snapshot entities"),
            @ApiResponse(code = 400, message = "Workspace ID is not specified"),
            @ApiResponse(code = 500, message = "Internal server error occurred") })
    public List<SnapshotDto> getSnapshots(
            @ApiParam(value = "Workspace ID", required = true) @QueryParam("workspace") String workspaceId)
            throws ServerException, BadRequestException {

        requiredNotNull(workspaceId, "Parameter workspace");

        final List<SnapshotImpl> snapshots = machineManager
                .getSnapshots(EnvironmentContext.getCurrent().getUser().getId(), workspaceId);

        return snapshots.stream().map(DtoConverter::asDto).map(this::injectLinks).collect(Collectors.toList());
    }

    @POST
    @Path("/{machineId}/snapshot")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    @RolesAllowed("user")
    @ApiOperation(value = "Save snapshot of machine")
    @ApiResponses({ @ApiResponse(code = 200, message = "The response contains requested snapshot entity"),
            @ApiResponse(code = 400, message = "Snapshot description is not specified"),
            @ApiResponse(code = 404, message = "Snapshot with specified ID does not exist"),
            @ApiResponse(code = 500, message = "Internal server error occurred") })
    public SnapshotDto saveSnapshot(@ApiParam(value = "Machine ID") @PathParam("machineId") String machineId,
            @ApiParam(value = "Snapshot description", required = true) NewSnapshotDescriptor newSnapshotDescriptor)
            throws NotFoundException, ServerException, ForbiddenException, BadRequestException {

        requiredNotNull(newSnapshotDescriptor, "Snapshot description");
        checkCurrentUserPermissions(machineManager.getMachine(machineId));

        return injectLinks(DtoConverter.asDto(machineManager.save(machineId,
                EnvironmentContext.getCurrent().getUser().getId(), newSnapshotDescriptor.getDescription())));
    }

    @DELETE
    @Path("/snapshot/{snapshotId}")
    @RolesAllowed("user")
    @ApiOperation(value = "Remove snapshot of machine")
    @ApiResponses({ @ApiResponse(code = 204, message = "Snapshot was successfully removed"),
            @ApiResponse(code = 404, message = "Snapshot with specified ID does not exist"),
            @ApiResponse(code = 500, message = "Internal server error occurred") })
    public void removeSnapshot(@ApiParam(value = "Snapshot ID") @PathParam("snapshotId") String snapshotId)
            throws ForbiddenException, NotFoundException, ServerException {

        checkCurrentUserPermissions(machineManager.getSnapshot(snapshotId));

        machineManager.removeSnapshot(snapshotId);
    }

    @POST
    @Path("/{machineId}/command")
    @Consumes(MediaType.APPLICATION_JSON)
    @RolesAllowed("user")
    @ApiOperation(value = "Start specified command in machine")
    @ApiResponses({ @ApiResponse(code = 200, message = "The response contains entity of created machine process"),
            @ApiResponse(code = 400, message = "Command entity is invalid"),
            @ApiResponse(code = 404, message = "Machine with specified ID does not exist"),
            @ApiResponse(code = 500, message = "Internal server error occurred") })
    public MachineProcessDto executeCommandInMachine(
            @ApiParam(value = "Machine ID") @PathParam("machineId") String machineId,
            @ApiParam(value = "Command to execute", required = true) final CommandDto command,
            @ApiParam(value = "Channel for command output", required = false) @QueryParam("outputChannel") String outputChannel)
            throws NotFoundException, ServerException, ForbiddenException, BadRequestException {

        requiredNotNull(command, "Command description");
        requiredNotNull(command.getCommandLine(), "Commandline");
        checkCurrentUserPermissions(machineManager.getMachine(machineId));

        return injectLinks(DtoConverter.asDto(machineManager.exec(machineId, command, outputChannel)), machineId);
    }

    @GET
    @Path("/{machineId}/process")
    @Produces(MediaType.APPLICATION_JSON)
    @RolesAllowed("user")
    @ApiOperation(value = "Get processes of machine", response = MachineProcessDto.class, responseContainer = "List")
    @ApiResponses({ @ApiResponse(code = 200, message = "The response contains machine process entities"),
            @ApiResponse(code = 404, message = "Machine with specified ID does not exist"),
            @ApiResponse(code = 500, message = "Internal server error occurred") })
    public List<MachineProcessDto> getProcesses(
            @ApiParam(value = "Machine ID") @PathParam("machineId") String machineId)
            throws NotFoundException, ServerException, ForbiddenException {

        checkCurrentUserPermissions(machineManager.getMachine(machineId));

        return machineManager.getProcesses(machineId).stream().map(DtoConverter::asDto)
                .map(machineProcess -> injectLinks(machineProcess, machineId)).collect(Collectors.toList());
    }

    @DELETE
    @Path("/{machineId}/process/{processId}")
    @RolesAllowed("user")
    @ApiOperation(value = "Stop process in machine")
    @ApiResponses({ @ApiResponse(code = 204, message = "Process was successfully stopped"),
            @ApiResponse(code = 404, message = "Machine with specified ID does not exist"),
            @ApiResponse(code = 500, message = "Internal server error occurred") })
    public void stopProcess(@ApiParam(value = "Machine ID") @PathParam("machineId") String machineId,
            @ApiParam(value = "Process ID") @PathParam("processId") int processId)
            throws NotFoundException, ForbiddenException, ServerException {

        checkCurrentUserPermissions(machineManager.getMachine(machineId));

        machineManager.stopProcess(machineId, processId);
    }

    @GET
    @Path("/{machineId}/logs")
    @Produces(MediaType.TEXT_PLAIN)
    @RolesAllowed("user")
    @ApiOperation(value = "Get logs of machine")
    @ApiResponses({ @ApiResponse(code = 200, message = "The response contains logs"),
            @ApiResponse(code = 404, message = "Machine with specified ID does not exist"),
            @ApiResponse(code = 500, message = "Internal server error occurred") })
    public void getMachineLogs(@ApiParam(value = "Machine ID") @PathParam("machineId") String machineId,
            @Context HttpServletResponse httpServletResponse)
            throws NotFoundException, ForbiddenException, ServerException, IOException {

        checkCurrentUserPermissions(machineManager.getMachine(machineId));

        addLogsToResponse(machineManager.getMachineLogReader(machineId), httpServletResponse);
    }

    @GET
    @Path("/{machineId}/process/{pid}/logs")
    @Produces(MediaType.TEXT_PLAIN)
    @RolesAllowed("user")
    @ApiOperation(value = "Get logs of machine process")
    @ApiResponses({ @ApiResponse(code = 200, message = "The response contains logs"),
            @ApiResponse(code = 404, message = "Machine or process with specified ID does not exist"),
            @ApiResponse(code = 500, message = "Internal server error occurred") })
    public void getProcessLogs(@ApiParam(value = "Machine ID") @PathParam("machineId") String machineId,
            @ApiParam(value = "Process ID") @PathParam("pid") int pid,
            @Context HttpServletResponse httpServletResponse)
            throws NotFoundException, ForbiddenException, ServerException, IOException {

        checkCurrentUserPermissions(machineManager.getMachine(machineId));

        addLogsToResponse(machineManager.getProcessLogReader(machineId, pid), httpServletResponse);
    }

    /**
     * Reads file content by specified file path.
     *
     * @param path
     *         path to file on machine instance
     * @param startFrom
     *         line number to start reading from
     * @param limit
     *         limitation on line if not specified will used 2000 lines
     * @return file content.
     * @throws MachineException
     *         if any error occurs with file reading
     */
    @GET
    @Path("/{machineId}/filepath/{path:.*}")
    @Produces(MediaType.TEXT_PLAIN)
    @RolesAllowed("user")
    @ApiOperation(value = "Get content of file in machine")
    @ApiResponses({ @ApiResponse(code = 200, message = "The response contains file content"),
            @ApiResponse(code = 404, message = "Machine with specified ID does not exist"),
            @ApiResponse(code = 500, message = "Internal server error occurred") })
    public String getFileContent(@ApiParam(value = "Machine ID") @PathParam("machineId") String machineId,
            @ApiParam(value = "Path of file") @PathParam("path") String path,
            @ApiParam(value = "From line") @QueryParam("startFrom") @DefaultValue("1") Integer startFrom,
            @ApiParam(value = "Number of lines") @QueryParam("limit") @DefaultValue("2000") Integer limit)
            throws NotFoundException, ForbiddenException, ServerException {

        final Instance machine = machineManager.getInstance(machineId);

        checkCurrentUserPermissions(machine);

        return machine.readFileContent(path, startFrom, limit);
    }

    /**
     * Copies files from specified machine into current machine.
     *
     * @param sourceMachineId
     *         source machine id
     * @param targetMachineId
     *         target machine id
     * @param sourcePath
     *         path to file or directory inside specified machine
     * @param targetPath
     *         path to destination file or directory inside machine
     * @param overwrite
     *         If "false" then it will be an error if unpacking the given content would cause
     *         an existing directory to be replaced with a non-directory and vice versa.
     * @throws MachineException
     *         if any error occurs when files are being copied
     * @throws NotFoundException
     *         if any error occurs with getting source machine
     */
    @POST
    @Path("/copy")
    @RolesAllowed("user")
    @ApiOperation(value = "Copy files from one machine to another")
    @ApiResponses({ @ApiResponse(code = 200, message = "Files were copied successfully"),
            @ApiResponse(code = 400, message = "Machine ID or path is not specified"),
            @ApiResponse(code = 404, message = "Source machine does not exist"),
            @ApiResponse(code = 404, message = "Target machine does not exist"),
            @ApiResponse(code = 500, message = "Internal server error occurred") })
    public void copyFilesBetweenMachines(
            @ApiParam(value = "Source machine ID", required = true) @QueryParam("sourceMachineId") String sourceMachineId,
            @ApiParam(value = "Target machine ID", required = true) @QueryParam("targetMachineId") String targetMachineId,
            @ApiParam(value = "Source path", required = true) @QueryParam("sourcePath") String sourcePath,
            @ApiParam(value = "Target path", required = true) @QueryParam("targetPath") String targetPath,
            @ApiParam(value = "Is files overwriting allowed") @QueryParam("overwrite") @DefaultValue("false") Boolean overwrite)
            throws NotFoundException, ServerException, ForbiddenException, BadRequestException {

        requiredNotNull(sourceMachineId, "Source machine id");
        requiredNotNull(targetMachineId, "Target machine id");
        requiredNotNull(sourcePath, "Source path");
        requiredNotNull(targetPath, "Target path");

        final Instance sourceMachine = machineManager.getInstance(sourceMachineId);
        final Instance targetMachine = machineManager.getInstance(targetMachineId);

        checkCurrentUserPermissions(sourceMachine);
        checkCurrentUserPermissions(targetMachine);

        targetMachine.copy(sourceMachine, sourcePath, targetPath, overwrite);
    }

    private MachineDto injectLinks(MachineDto machine) {
        return injectLinks(machine, getServiceContext());
    }

    public static MachineDto injectLinks(MachineDto machine, ServiceContext serviceContext) {
        final UriBuilder uriBuilder = serviceContext.getServiceUriBuilder();
        final List<Link> links = new ArrayList<>();

        links.add(createLink(HttpMethod.GET,
                uriBuilder.clone().path(MachineService.class, "getMachineById").build(machine.getId()).toString(),
                APPLICATION_JSON, "self link"));
        links.add(createLink(HttpMethod.GET,
                uriBuilder.clone().path(MachineService.class, "getMachines").build().toString(), null,
                APPLICATION_JSON, Constants.LINK_REL_GET_MACHINES, newDto(LinkParameter.class).withName("workspace")
                        .withRequired(true).withDefaultValue(machine.getWorkspaceId())));
        links.add(createLink(HttpMethod.DELETE,
                uriBuilder.clone().path(MachineService.class, "destroyMachine").build(machine.getId()).toString(),
                Constants.LINK_REL_DESTROY_MACHINE));
        links.add(createLink(HttpMethod.GET,
                uriBuilder.clone().path(MachineService.class, "getSnapshots").build().toString(), null,
                APPLICATION_JSON, Constants.LINK_REL_GET_SNAPSHOTS, newDto(LinkParameter.class)
                        .withName("workspace").withRequired(true).withDefaultValue(machine.getWorkspaceId())));
        links.add(createLink(HttpMethod.POST,
                uriBuilder.clone().path(MachineService.class, "saveSnapshot").build(machine.getId()).toString(),
                APPLICATION_JSON, APPLICATION_JSON, Constants.LINK_REL_SAVE_SNAPSHOT));
        links.add(createLink(HttpMethod.POST,
                uriBuilder.clone().path(MachineService.class, "executeCommandInMachine").build(machine.getId())
                        .toString(),
                APPLICATION_JSON, APPLICATION_JSON, Constants.LINK_REL_EXECUTE_COMMAND,
                newDto(LinkParameter.class).withName("outputChannel").withRequired(false)));
        links.add(createLink(HttpMethod.GET,
                uriBuilder.clone().path(MachineService.class, "getProcesses").build(machine.getId()).toString(),
                APPLICATION_JSON, Constants.LINK_REL_GET_PROCESSES));
        final URI getLogsUri = uriBuilder.clone().path(MachineService.class, "getMachineLogs")
                .build(machine.getId());
        links.add(
                createLink(HttpMethod.GET, getLogsUri.toString(), TEXT_PLAIN, Constants.LINK_REL_GET_MACHINE_LOGS));

        // add links to websocket channels
        final Link machineChannelLink = createLink("GET",
                serviceContext.getBaseUriBuilder().path("ws").path(machine.getWorkspaceId())
                        .scheme("https".equals(getLogsUri.getScheme()) ? "wss" : "ws").build().toString(),
                null);
        final LinkParameter channelParameter = newDto(LinkParameter.class).withName("channel").withRequired(true);

        injectMachineChannelsLinks(machine.getConfig(), machine.getWorkspaceId(), machine.getEnvName(),
                machineChannelLink, channelParameter);

        return machine.withLinks(links);
    }

    public static void injectMachineChannelsLinks(MachineConfigDto machineConfig, String workspaceId,
            String envName, Link machineChannelLink, LinkParameter channelParameter) {
        final ChannelsImpl channels = MachineManager.getMachineChannels(machineConfig.getName(), workspaceId,
                envName);
        final Link getLogsLink = cloneDto(machineChannelLink)
                .withRel(org.eclipse.che.api.machine.shared.Constants.LINK_REL_GET_MACHINE_LOGS_CHANNEL)
                .withParameters(singletonList(cloneDto(channelParameter).withDefaultValue(channels.getOutput())));

        final Link getStatusLink = cloneDto(machineChannelLink)
                .withRel(org.eclipse.che.api.machine.shared.Constants.LINK_REL_GET_MACHINE_STATUS_CHANNEL)
                .withParameters(singletonList(cloneDto(channelParameter).withDefaultValue(channels.getStatus())));

        machineConfig.withLinks(asList(getLogsLink, getStatusLink));
    }

    private MachineProcessDto injectLinks(MachineProcessDto process, String machineId) {
        final UriBuilder uriBuilder = getServiceContext().getServiceUriBuilder();
        final List<Link> links = Lists.newArrayListWithExpectedSize(3);

        links.add(createLink(HttpMethod.DELETE,
                uriBuilder.clone().path(getClass(), "stopProcess").build(machineId, process.getPid()).toString(),
                Constants.LINK_REL_STOP_PROCESS));
        links.add(createLink(HttpMethod.GET,
                uriBuilder.clone().path(getClass(), "getProcessLogs").build(machineId, process.getPid()).toString(),
                TEXT_PLAIN, Constants.LINK_REL_GET_PROCESS_LOGS));
        links.add(createLink(HttpMethod.GET,
                uriBuilder.clone().path(getClass(), "getProcesses").build(machineId).toString(), APPLICATION_JSON,
                Constants.LINK_REL_GET_PROCESSES));

        return process.withLinks(links);
    }

    private SnapshotDto injectLinks(SnapshotDto snapshot) {
        final UriBuilder uriBuilder = getServiceContext().getServiceUriBuilder();

        return snapshot.withLinks(singletonList(createLink(HttpMethod.DELETE,
                uriBuilder.clone().path(getClass(), "removeSnapshot").build(snapshot.getId()).toString(),
                Constants.LINK_REL_REMOVE_SNAPSHOT)));
    }

    private void addLogsToResponse(Reader logsReader, HttpServletResponse httpServletResponse) throws IOException {
        // Response is written directly to the servlet request stream
        httpServletResponse.setContentType("text/plain");
        CharStreams.copy(logsReader, httpServletResponse.getWriter());
        httpServletResponse.getWriter().flush();
    }

    private void checkCurrentUserPermissions(SnapshotImpl snapshot) throws ForbiddenException, ServerException {
        checkCurrentUserPermissions(snapshot.getWorkspaceId());
    }

    private void checkCurrentUserPermissions(Machine machine) throws ForbiddenException, ServerException {
        checkCurrentUserPermissions(machine.getWorkspaceId());
    }

    private void checkCurrentUserPermissions(String workspaceId) throws ForbiddenException, ServerException {
        // TODO
        //        try {
        //            final Member member = memberDao.getWorkspaceMember(workspaceId, EnvironmentContext.getCurrent().getUser().getId());
        //            if (member.getRoles().contains("workspace/admin") || member.getRoles().contains("workspace/developer")) {
        //                return;
        //            }
        //        } catch (NotFoundException ignored) {
        //        }
        //        throw new ForbiddenException("You are not a member of workspace " + workspaceId);
    }

    /**
     * Checks object reference is not {@code null}
     *
     * @param object
     *         object reference to check
     * @param subject
     *         used as subject of exception message "{subject} required"
     * @throws BadRequestException
     *         when object reference is {@code null}
     */
    private void requiredNotNull(Object object, String subject) throws BadRequestException {
        if (object == null) {
            throw new BadRequestException(subject + " required");
        }
    }
}