org.eclipse.che.api.project.server.ProjectService.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.che.api.project.server.ProjectService.java

Source

/*******************************************************************************
 * 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.project.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 org.apache.commons.fileupload.FileItem;
import org.apache.tika.Tika;
import org.eclipse.che.WorkspaceIdProvider;
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.UnauthorizedException;
import org.eclipse.che.api.core.model.project.type.Value;
import org.eclipse.che.api.core.notification.EventService;
import org.eclipse.che.api.core.rest.Service;
import org.eclipse.che.api.core.rest.annotations.Description;
import org.eclipse.che.api.core.rest.annotations.GenerateLink;
import org.eclipse.che.api.project.server.importer.ProjectImportOutputWSLineConsumer;
import org.eclipse.che.api.project.server.notification.ProjectItemModifiedEvent;
import org.eclipse.che.api.project.server.type.ProjectTypeResolution;
import org.eclipse.che.api.project.shared.dto.CopyOptions;
import org.eclipse.che.api.project.shared.dto.ItemReference;
import org.eclipse.che.api.project.shared.dto.MoveOptions;
import org.eclipse.che.api.project.shared.dto.SourceEstimation;
import org.eclipse.che.api.project.shared.dto.TreeElement;
import org.eclipse.che.api.vfs.VirtualFile;
import org.eclipse.che.api.vfs.search.QueryExpression;
import org.eclipse.che.api.vfs.search.SearchResult;
import org.eclipse.che.api.vfs.search.SearchResultEntry;
import org.eclipse.che.api.vfs.search.Searcher;
import org.eclipse.che.api.workspace.shared.dto.NewProjectConfigDto;
import org.eclipse.che.api.workspace.shared.dto.ProjectConfigDto;
import org.eclipse.che.api.workspace.shared.dto.SourceStorageDto;
import org.eclipse.che.commons.env.EnvironmentContext;
import org.eclipse.che.commons.lang.ws.rs.ExtMediaType;
import org.eclipse.che.dto.server.DtoFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import javax.inject.Singleton;
import javax.validation.constraints.NotNull;
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.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static org.eclipse.che.api.project.server.DtoConverter.asDto;
import static org.eclipse.che.api.project.shared.Constants.LINK_REL_CREATE_BATCH_PROJECTS;
import static org.eclipse.che.api.project.shared.Constants.LINK_REL_CREATE_PROJECT;
import static org.eclipse.che.api.project.shared.Constants.LINK_REL_GET_PROJECTS;
import static org.eclipse.che.dto.server.DtoFactory.newDto;

/**
 * Project API.
 *
 * @author andrew00x
 * @author Eugene Voevodin
 * @author Artem Zatsarynnyi
 * @author Valeriy Svydenko
 * @author Dmitry Shnurenko
 */
@Api(value = "/project", description = "Project REST API")
@Path("/project")
@Singleton
public class ProjectService extends Service {
    private static final Logger LOG = LoggerFactory.getLogger(ProjectService.class);
    private static final Tika TIKA = new Tika();

    private final ProjectManager projectManager;
    private final EventService eventService;
    private final ProjectServiceLinksInjector projectServiceLinksInjector;
    private final String workspace;

    @Inject
    public ProjectService(ProjectManager projectManager, EventService eventService,
            ProjectServiceLinksInjector projectServiceLinksInjector) {
        this.projectManager = projectManager;
        this.eventService = eventService;
        this.projectServiceLinksInjector = projectServiceLinksInjector;
        this.workspace = WorkspaceIdProvider.getWorkspaceId();
    }

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation(value = "Gets list of projects in root folder", response = ProjectConfigDto.class, responseContainer = "List")
    @ApiResponses({ @ApiResponse(code = 200, message = "OK"), @ApiResponse(code = 500, message = "Server error") })
    @GenerateLink(rel = LINK_REL_GET_PROJECTS)
    public List<ProjectConfigDto> getProjects()
            throws IOException, ServerException, ConflictException, ForbiddenException {
        return projectManager.getProjects().stream().map(p -> injectProjectLinks(asDto(p)))
                .collect(Collectors.toList());
    }

    @GET
    @Path("/{path:.*}")
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation(value = "Gets project by ID of workspace and project's path", response = ProjectConfigDto.class)
    @ApiResponses({ @ApiResponse(code = 200, message = "OK"),
            @ApiResponse(code = 404, message = "Project with specified path doesn't exist in workspace"),
            @ApiResponse(code = 403, message = "Access to requested project is forbidden"),
            @ApiResponse(code = 500, message = "Server error") })
    public ProjectConfigDto getProject(
            @ApiParam(value = "Path to requested project", required = true) @PathParam("path") String path)
            throws NotFoundException, ForbiddenException, ServerException, ConflictException {
        return injectProjectLinks(asDto(projectManager.getProject(path)));
    }

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation(value = "Creates new project", response = ProjectConfigDto.class)
    @ApiResponses({ @ApiResponse(code = 200, message = "OK"),
            @ApiResponse(code = 403, message = "Operation is forbidden"),
            @ApiResponse(code = 409, message = "Project with specified name already exist in workspace"),
            @ApiResponse(code = 500, message = "Server error") })
    @GenerateLink(rel = LINK_REL_CREATE_PROJECT)
    /**
     * NOTE: parentPath is added to make a module
     */
    public ProjectConfigDto createProject(
            @ApiParam(value = "Add to this project as module", required = false) @Context UriInfo uriInfo,
            @Description("descriptor of project") ProjectConfigDto projectConfig)
            throws ConflictException, ForbiddenException, ServerException, NotFoundException {
        Map<String, String> options = new HashMap<>();
        MultivaluedMap<String, String> map = uriInfo.getQueryParameters();
        for (String key : map.keySet()) {
            options.put(key, map.get(key).get(0));
        }
        String pathToProject = projectConfig.getPath();
        String pathToParent = pathToProject.substring(0, pathToProject.lastIndexOf("/"));

        if (!pathToParent.equals("/")) {
            VirtualFileEntry parentFileEntry = projectManager.getProjectsRoot().getChild(pathToParent);
            if (parentFileEntry == null) {
                throw new NotFoundException("The parent folder with path " + pathToParent + " does not exist.");
            }
        }

        final RegisteredProject project = projectManager.createProject(projectConfig, options);
        final ProjectConfigDto configDto = asDto(project);

        eventService.publish(new ProjectCreatedEvent(workspace, project.getPath()));

        // TODO this throws NPE
        //logProjectCreatedEvent(configDto.getName(), configDto.getProjectType());

        return injectProjectLinks(configDto);
    }

    @POST
    @Path("/batch")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation(value = "Creates batch of projects according to their configurations", notes = "A project will be created by importing when project configuration contains source object. "
            + "For creating a project by generator options should be specified.", response = ProjectConfigDto.class)
    @ApiResponses({ @ApiResponse(code = 200, message = "OK"),
            @ApiResponse(code = 400, message = "Path for new project should be defined"),
            @ApiResponse(code = 403, message = "Operation is forbidden"),
            @ApiResponse(code = 409, message = "Project with specified name already exist in workspace"),
            @ApiResponse(code = 500, message = "Server error") })
    @GenerateLink(rel = LINK_REL_CREATE_BATCH_PROJECTS)
    public List<ProjectConfigDto> createBatchProjects(
            @Description("list of descriptors for projects") List<NewProjectConfigDto> projectConfigList,
            @ApiParam(value = "Force rewrite existing project", allowableValues = "true,false") @QueryParam("force") boolean rewrite)
            throws ConflictException, ForbiddenException, ServerException, NotFoundException, IOException,
            UnauthorizedException, BadRequestException {

        List<ProjectConfigDto> result = new ArrayList<>(projectConfigList.size());
        final ProjectOutputLineConsumerFactory outputOutputConsumerFactory = new ProjectOutputLineConsumerFactory(
                workspace, 300);

        for (RegisteredProject registeredProject : projectManager.createBatchProjects(projectConfigList, rewrite,
                outputOutputConsumerFactory)) {

            ProjectConfigDto projectConfig = injectProjectLinks(asDto(registeredProject));
            result.add(projectConfig);

            eventService.publish(new ProjectCreatedEvent(workspace, registeredProject.getPath()));
        }
        return result;
    }

    @PUT
    @Path("/{path:.*}")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation(value = "Updates existing project", response = ProjectConfigDto.class)
    @ApiResponses({ @ApiResponse(code = 200, message = "OK"),
            @ApiResponse(code = 404, message = "Project with specified path doesn't exist in workspace"),
            @ApiResponse(code = 403, message = "Operation is forbidden"),
            @ApiResponse(code = 409, message = "Update operation causes conflicts"),
            @ApiResponse(code = 500, message = "Server error") })
    public ProjectConfigDto updateProject(
            @ApiParam(value = "Path to updated project", required = true) @PathParam("path") String path,
            ProjectConfigDto projectConfigDto)
            throws NotFoundException, ConflictException, ForbiddenException, ServerException, IOException {
        if (path != null) {
            projectConfigDto.setPath(path);
        }

        return asDto(projectManager.updateProject(projectConfigDto));
    }

    @DELETE
    @Path("/{path:.*}")
    @ApiOperation(value = "Delete a resource", notes = "Delete resources. If you want to delete a single project, specify project name. If a folder or file needs to "
            + "be deleted a path to the requested resource needs to be specified")
    @ApiResponses({ @ApiResponse(code = 204, message = ""),
            @ApiResponse(code = 403, message = "User not authorized to call this operation"),
            @ApiResponse(code = 404, message = "Not found"),
            @ApiResponse(code = 500, message = "Internal Server Error") })
    public void delete(@ApiParam("Path to a resource to be deleted") @PathParam("path") String path)
            throws NotFoundException, ForbiddenException, ConflictException, ServerException {
        projectManager.delete(path);
    }

    @GET
    @Path("/estimate/{path:.*}")
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation(value = "Estimates if the folder supposed to be project of certain type", response = Map.class)
    @ApiResponses({ @ApiResponse(code = 200, message = "OK"),
            @ApiResponse(code = 404, message = "Project with specified path doesn't exist in workspace"),
            @ApiResponse(code = 403, message = "Access to requested project is forbidden"),
            @ApiResponse(code = 500, message = "Server error") })
    public SourceEstimation estimateProject(
            @ApiParam(value = "Path to requested project", required = true) @PathParam("path") String path,
            @ApiParam(value = "Project Type ID to estimate against", required = true) @QueryParam("type") String projectType)
            throws NotFoundException, ForbiddenException, ServerException, ConflictException {
        final ProjectTypeResolution resolution = projectManager.estimateProject(path, projectType);

        final HashMap<String, List<String>> attributes = new HashMap<>();
        for (Map.Entry<String, Value> attr : resolution.getProvidedAttributes().entrySet()) {
            attributes.put(attr.getKey(), attr.getValue().getList());
        }

        return DtoFactory.newDto(SourceEstimation.class).withType(projectType).withMatched(resolution.matched())
                .withResolution(resolution.getResolution()).withAttributes(attributes);
    }

    @GET
    @Path("/resolve/{path:.*}")
    @Produces(MediaType.APPLICATION_JSON)
    public List<SourceEstimation> resolveSources(
            @ApiParam(value = "Path to requested project", required = true) @PathParam("path") String path)
            throws NotFoundException, ForbiddenException, ServerException, ConflictException {
        List<SourceEstimation> estimations = new ArrayList<>();
        for (ProjectTypeResolution resolution : projectManager.resolveSources(path, false)) {
            if (resolution.matched()) {
                final HashMap<String, List<String>> attributes = new HashMap<>();
                for (Map.Entry<String, Value> attr : resolution.getProvidedAttributes().entrySet()) {
                    attributes.put(attr.getKey(), attr.getValue().getList());
                }
                estimations.add(DtoFactory.newDto(SourceEstimation.class).withType(resolution.getType())
                        .withMatched(resolution.matched()).withAttributes(attributes));
            }
        }

        return estimations;
    }

    @POST
    @Path("/import/{path:.*}")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation(value = "Import resource", notes = "Import resource. JSON with a designated importer and project location is sent. It is possible to import from "
            + "VCS or ZIP")
    @ApiResponses({ @ApiResponse(code = 204, message = ""),
            @ApiResponse(code = 401, message = "User not authorized to call this operation"),
            @ApiResponse(code = 403, message = "Forbidden operation"),
            @ApiResponse(code = 409, message = "Resource already exists"),
            @ApiResponse(code = 500, message = "Unsupported source type") })
    public void importProject(
            @ApiParam(value = "Path in the project", required = true) @PathParam("path") String path,
            @ApiParam(value = "Force rewrite existing project", allowableValues = "true,false") @QueryParam("force") boolean force,
            SourceStorageDto sourceStorage) throws ConflictException, ForbiddenException, UnauthorizedException,
            IOException, ServerException, NotFoundException, BadRequestException {
        projectManager.importProject(path, sourceStorage, force,
                () -> new ProjectImportOutputWSLineConsumer(path, workspace, 300));
    }

    @POST
    @Path("/file/{parent:.*}")
    @Consumes({ MediaType.MEDIA_TYPE_WILDCARD })
    @Produces({ MediaType.APPLICATION_JSON })
    @ApiOperation(value = "Create file", notes = "Create a new file in a project. If file type isn't specified the server will resolve its type.")
    @ApiResponses({ @ApiResponse(code = 201, message = ""),
            @ApiResponse(code = 403, message = "User not authorized to call this operation"),
            @ApiResponse(code = 404, message = "Not found"),
            @ApiResponse(code = 409, message = "File already exists"),
            @ApiResponse(code = 500, message = "Internal Server Error") })
    public Response createFile(
            @ApiParam(value = "Path to a target directory", required = true) @PathParam("parent") String parentPath,
            @ApiParam(value = "New file name", required = true) @QueryParam("name") String fileName,
            InputStream content) throws NotFoundException, ConflictException, ForbiddenException, ServerException {
        final FolderEntry parent = projectManager.asFolder(parentPath);

        if (parent == null) {
            throw new NotFoundException("Parent not found for " + parentPath);
        }

        final FileEntry newFile = parent.createFile(fileName, content);

        eventService.publish(new ProjectItemModifiedEvent(ProjectItemModifiedEvent.EventType.CREATED, workspace,
                newFile.getProject(), newFile.getPath().toString(), false));

        final URI location = getServiceContext().getServiceUriBuilder().clone().path(getClass(), "getFile")
                .build(new String[] { newFile.getPath().toString().substring(1) }, false);
        return Response.created(location).entity(injectFileLinks(asDto(newFile))).build();
    }

    @POST
    @Path("/folder/{path:.*}")
    @Produces({ MediaType.APPLICATION_JSON })
    @ApiOperation(value = "Create a folder", notes = "Create a folder is a specified project")
    @ApiResponses({ @ApiResponse(code = 201, message = ""),
            @ApiResponse(code = 403, message = "User not authorized to call this operation"),
            @ApiResponse(code = 404, message = "Not found"),
            @ApiResponse(code = 409, message = "File already exists"),
            @ApiResponse(code = 500, message = "Internal Server Error") })
    public Response createFolder(
            @ApiParam(value = "Path to a new folder destination", required = true) @PathParam("path") String path)
            throws ConflictException, ForbiddenException, ServerException, NotFoundException {
        final FolderEntry newFolder = projectManager.getProjectsRoot().createFolder(path);
        final URI location = getServiceContext().getServiceUriBuilder().clone().path(getClass(), "getChildren")
                .build(new String[] { newFolder.getPath().toString().substring(1) }, false);

        eventService.publish(new ProjectItemModifiedEvent(ProjectItemModifiedEvent.EventType.CREATED, workspace,
                newFolder.getProject(), newFolder.getPath().toString(), true));

        return Response.created(location).entity(injectFolderLinks(asDto(newFolder))).build();
    }

    @POST
    @Path("/uploadfile/{parent:.*}")
    @Consumes({ MediaType.MULTIPART_FORM_DATA })
    @Produces({ MediaType.TEXT_HTML })
    @ApiOperation(value = "Upload a file", notes = "Upload a new file")
    @ApiResponses({ @ApiResponse(code = 201, message = ""),
            @ApiResponse(code = 403, message = "User not authorized to call this operation"),
            @ApiResponse(code = 404, message = "Not found"),
            @ApiResponse(code = 409, message = "File already exists"),
            @ApiResponse(code = 500, message = "Internal Server Error") })
    public Response uploadFile(
            @ApiParam(value = "Destination path", required = true) @PathParam("parent") String parentPath,
            Iterator<FileItem> formData)
            throws NotFoundException, ConflictException, ForbiddenException, ServerException {
        final FolderEntry parent = projectManager.asFolder(parentPath);

        if (parent == null) {
            throw new NotFoundException("Parent not found for " + parentPath);
        }

        return uploadFile(parent.getVirtualFile(), formData);
    }

    @POST
    @Path("/upload/zipfolder/{path:.*}")
    @Consumes({ MediaType.MULTIPART_FORM_DATA })
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation(value = "Upload zip folder", notes = "Upload folder from local zip", response = Response.class)
    @ApiResponses({ @ApiResponse(code = 200, message = ""),
            @ApiResponse(code = 401, message = "User not authorized to call this operation"),
            @ApiResponse(code = 403, message = "Forbidden operation"),
            @ApiResponse(code = 404, message = "Not found"),
            @ApiResponse(code = 409, message = "Resource already exists"),
            @ApiResponse(code = 500, message = "Internal Server Error") })
    public Response uploadFolderFromZip(
            @ApiParam(value = "Path in the project", required = true) @PathParam("path") String path,
            Iterator<FileItem> formData)
            throws ServerException, ConflictException, ForbiddenException, NotFoundException {
        final FolderEntry parent = projectManager.asFolder(path);

        if (parent == null) {
            throw new NotFoundException("Parent not found for " + path);
        }

        return uploadZip(parent.getVirtualFile(), formData);
    }

    @ApiOperation(value = "Get file content", notes = "Get file content by its name")
    @ApiResponses({ @ApiResponse(code = 200, message = "OK"),
            @ApiResponse(code = 403, message = "User not authorized to call this operation"),
            @ApiResponse(code = 404, message = "Not found"),
            @ApiResponse(code = 500, message = "Internal Server Error") })
    @GET
    @Path("/file/{path:.*}")
    public Response getFile(@ApiParam(value = "Path to a file", required = true) @PathParam("path") String path)
            throws IOException, NotFoundException, ForbiddenException, ServerException {
        final FileEntry file = projectManager.asFile(path);
        if (file == null) {
            throw new NotFoundException("File not found for " + path);
        }
        return Response.ok().entity(file.getInputStream()).type(TIKA.detect(file.getName())).build();
    }

    @PUT
    @Path("/file/{path:.*}")
    @Consumes({ MediaType.MEDIA_TYPE_WILDCARD })
    @ApiOperation(value = "Update file", notes = "Update an existing file with new content")
    @ApiResponses({ @ApiResponse(code = 200, message = ""),
            @ApiResponse(code = 403, message = "User not authorized to call this operation"),
            @ApiResponse(code = 404, message = "Not found"),
            @ApiResponse(code = 500, message = "Internal Server Error") })
    public Response updateFile(
            @ApiParam(value = "Full path to a file", required = true) @PathParam("path") String path,
            InputStream content) throws NotFoundException, ForbiddenException, ServerException {
        final FileEntry file = projectManager.asFile(path);

        if (file == null) {
            throw new NotFoundException("File not found for " + path);
        }

        file.updateContent(content);

        eventService.publish(new ProjectItemModifiedEvent(ProjectItemModifiedEvent.EventType.UPDATED, workspace,
                file.getProject(), file.getPath().toString(), false));

        return Response.ok().build();
    }

    @POST
    @Path("/copy/{path:.*}")
    @Consumes(MediaType.APPLICATION_JSON)
    @ApiOperation(value = "Copy resource", notes = "Copy resource to a new location which is specified in a query parameter")
    @ApiResponses({ @ApiResponse(code = 201, message = ""),
            @ApiResponse(code = 403, message = "User not authorized to call this operation"),
            @ApiResponse(code = 404, message = "Not found"),
            @ApiResponse(code = 409, message = "Resource already exists"),
            @ApiResponse(code = 500, message = "Internal Server Error") })
    public Response copy(@ApiParam("Path to a resource") @PathParam("path") String path,
            @ApiParam(value = "Path to a new location", required = true) @QueryParam("to") String newParent,
            CopyOptions copyOptions)
            throws NotFoundException, ForbiddenException, ConflictException, ServerException {
        final VirtualFileEntry entry = projectManager.asVirtualFileEntry(path);

        // used to indicate over write of destination
        boolean isOverWrite = false;
        // used to hold new name set in request body
        String newName = entry.getName();
        if (copyOptions != null) {
            if (copyOptions.getOverWrite() != null) {
                isOverWrite = copyOptions.getOverWrite();
            }
            if (copyOptions.getName() != null) {
                newName = copyOptions.getName();
            }
        }

        final VirtualFileEntry copy = projectManager.copyTo(path, newParent, newName, isOverWrite);

        final URI location = getServiceContext().getServiceUriBuilder()
                .path(getClass(), copy.isFile() ? "getFile" : "getChildren")
                .build(new String[] { copy.getPath().toString().substring(1) }, false);

        if (copy.isFolder()) {
            try {
                final RegisteredProject project = projectManager.getProject(copy.getPath().toString());
                final String name = project.getName();
                final String projectType = project.getProjectType().getId();
                logProjectCreatedEvent(name, projectType);
            } catch (NotFoundException ignore) {
            }
        }

        return Response.created(location).build();
    }

    @POST
    @Path("/move/{path:.*}")
    @Consumes(MediaType.APPLICATION_JSON)
    @ApiOperation(value = "Move resource", notes = "Move resource to a new location which is specified in a query parameter")
    @ApiResponses({ @ApiResponse(code = 201, message = ""),
            @ApiResponse(code = 403, message = "User not authorized to call this operation"),
            @ApiResponse(code = 404, message = "Not found"),
            @ApiResponse(code = 409, message = "Resource already exists"),
            @ApiResponse(code = 500, message = "Internal Server Error") })
    public Response move(@ApiParam("Path to a resource to be moved") @PathParam("path") String path,
            @ApiParam("Path to a new location") @QueryParam("to") String newParent, MoveOptions moveOptions)
            throws NotFoundException, ForbiddenException, ConflictException, ServerException {
        final VirtualFileEntry entry = projectManager.asVirtualFileEntry(path);

        // used to indicate over write of destination
        boolean isOverWrite = false;
        // used to hold new name set in request body
        String newName = entry.getName();
        if (moveOptions != null) {
            if (moveOptions.getOverWrite() != null) {
                isOverWrite = moveOptions.getOverWrite();
            }
            if (moveOptions.getName() != null) {
                newName = moveOptions.getName();
            }
        }

        final VirtualFileEntry move = projectManager.moveTo(path, newParent, newName, isOverWrite);

        final URI location = getServiceContext().getServiceUriBuilder()
                .path(getClass(), move.isFile() ? "getFile" : "getChildren")
                .build(new String[] { move.getPath().toString().substring(1) }, false);

        return Response.created(location).build();
    }

    @POST
    @Path("/upload/zipproject/{path:.*}")
    @Consumes({ MediaType.MULTIPART_FORM_DATA })
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation(value = "Upload zip project", notes = "Upload project from local zip", response = ProjectConfigDto.class)
    @ApiResponses({ @ApiResponse(code = 200, message = ""),
            @ApiResponse(code = 401, message = "User not authorized to call this operation"),
            @ApiResponse(code = 403, message = "Forbidden operation"),
            @ApiResponse(code = 409, message = "Resource already exists"),
            @ApiResponse(code = 500, message = "Unsupported source type") })
    public List<SourceEstimation> uploadProjectFromZip(
            @ApiParam(value = "Path in the project", required = true) @PathParam("path") String path,
            @ApiParam(value = "Force rewrite existing project", allowableValues = "true,false") @QueryParam("force") boolean force,
            Iterator<FileItem> formData) throws ServerException, IOException, ConflictException, ForbiddenException,
            NotFoundException, BadRequestException {
        // Not all importers uses virtual file system API. In this case virtual file system API doesn't get events and isn't able to set
        final FolderEntry baseProjectFolder = (FolderEntry) getVirtualFile(path, force);

        int stripNumber = 0;
        String projectName = "";
        String projectDescription = "";
        FileItem contentItem = null;

        while (formData.hasNext()) {
            FileItem item = formData.next();
            if (!item.isFormField()) {
                if (contentItem == null) {
                    contentItem = item;
                } else {
                    throw new ServerException("More then one upload file is found but only one is expected. ");
                }
            } else {
                switch (item.getFieldName()) {
                case ("name"):
                    projectName = item.getString().trim();
                    break;
                case ("description"):
                    projectDescription = item.getString().trim();
                    break;
                case ("skipFirstLevel"):
                    stripNumber = Boolean.parseBoolean(item.getString().trim()) ? 1 : 0;
                    break;
                }
            }
        }

        if (contentItem == null) {
            throw new ServerException("Cannot find zip file for upload.");
        }

        try (InputStream zip = contentItem.getInputStream()) {
            baseProjectFolder.getVirtualFile().unzip(zip, true, stripNumber);
        }

        return resolveSources(path);
    }

    @POST
    @Path("/import/{path:.*}")
    @Consumes(ExtMediaType.APPLICATION_ZIP)
    @ApiOperation(value = "Import zip", notes = "Import resources as zip")
    @ApiResponses({ @ApiResponse(code = 201, message = ""),
            @ApiResponse(code = 403, message = "User not authorized to call this operation"),
            @ApiResponse(code = 404, message = "Not found"),
            @ApiResponse(code = 409, message = "Resource already exists"),
            @ApiResponse(code = 500, message = "Internal Server Error") })
    public Response importZip(
            @ApiParam(value = "Path to a location (where import to?)") @PathParam("path") String path,
            InputStream zip, @DefaultValue("false") @QueryParam("skipFirstLevel") Boolean skipFirstLevel)
            throws NotFoundException, ConflictException, ForbiddenException, ServerException {
        final FolderEntry parent = projectManager.asFolder(path);

        if (parent == null) {
            throw new NotFoundException("Parent not found for " + path);
        }

        importZip(parent.getVirtualFile(), zip, true, skipFirstLevel);

        try {
            final RegisteredProject project = projectManager.getProject(path);
            eventService.publish(new ProjectCreatedEvent(workspace, project.getPath()));
            final String projectType = project.getProjectType().getId();
            logProjectCreatedEvent(path, projectType);
        } catch (NotFoundException ignore) {
        }

        return Response.created(getServiceContext().getServiceUriBuilder().path(getClass(), "getChildren")
                .build(new String[] { parent.getPath().toString().substring(1) }, false)).build();
    }

    @GET
    @Path("/export/{path:.*}")
    @Produces(ExtMediaType.APPLICATION_ZIP)
    @ApiOperation(value = "Download ZIP", notes = "Export resource as zip. It can be an entire project or folder")
    @ApiResponses({ @ApiResponse(code = 201, message = ""),
            @ApiResponse(code = 403, message = "User not authorized to call this operation"),
            @ApiResponse(code = 404, message = "Not found"),
            @ApiResponse(code = 500, message = "Internal Server Error") })
    public InputStream exportZip(
            @ApiParam(value = "Path to resource to be exported") @PathParam("path") String path)
            throws NotFoundException, ForbiddenException, ServerException {

        final FolderEntry folder = projectManager.asFolder(path);

        if (folder == null) {
            throw new NotFoundException("Folder not found " + path);
        }

        return folder.getVirtualFile().zip();
    }

    @GET
    @Path("/export/file/{path:.*}")
    @Produces(MediaType.APPLICATION_OCTET_STREAM)
    public Response exportFile(@ApiParam(value = "Path to resource to be imported") @PathParam("path") String path)
            throws NotFoundException, ForbiddenException, ServerException {

        final FileEntry file = projectManager.asFile(path);

        if (file == null) {
            throw new NotFoundException("File not found " + path);
        }

        final VirtualFile virtualFile = file.getVirtualFile();

        return Response.ok(virtualFile.getContent(), TIKA.detect(virtualFile.getName()))
                .lastModified(new Date(virtualFile.getLastModificationDate()))
                .header(HttpHeaders.CONTENT_LENGTH, Long.toString(virtualFile.getLength()))
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + virtualFile.getName() + '"')
                .build();
    }

    @GET
    @Path("/children/{parent:.*}")
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation(value = "Get project children items", notes = "Request all children items for a project, such as files and folders", response = ItemReference.class, responseContainer = "List")
    @ApiResponses({ @ApiResponse(code = 200, message = "OK"),
            @ApiResponse(code = 403, message = "User not authorized to call this operation"),
            @ApiResponse(code = 404, message = "Not found"),
            @ApiResponse(code = 500, message = "Internal Server Error") })
    public List<ItemReference> getChildren(
            @ApiParam(value = "Path to a project", required = true) @PathParam("parent") String path)
            throws NotFoundException, ForbiddenException, ServerException {
        final FolderEntry folder = projectManager.asFolder(path);

        if (folder == null) {
            throw new NotFoundException("Parent not found for " + path);
        }

        final List<VirtualFileEntry> children = folder.getChildren();
        final ArrayList<ItemReference> result = new ArrayList<>(children.size());
        for (VirtualFileEntry child : children) {
            if (child.isFile()) {
                result.add(injectFileLinks(asDto((FileEntry) child)));
            } else {
                result.add(injectFolderLinks(asDto((FolderEntry) child)));
            }
        }

        return result;
    }

    @GET
    @Path("/tree/{parent:.*}")
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation(value = "Get project tree", notes = "Get project tree. Depth is specified in a query parameter", response = TreeElement.class)
    @ApiResponses({ @ApiResponse(code = 200, message = "OK"),
            @ApiResponse(code = 403, message = "User not authorized to call this operation"),
            @ApiResponse(code = 404, message = "Not found"),
            @ApiResponse(code = 500, message = "Internal Server Error") })
    public TreeElement getTree(
            @ApiParam(value = "Path to resource. Can be project or its folders", required = true) @PathParam("parent") String path,
            @ApiParam(value = "Tree depth. This parameter can be dropped. If not specified ?depth=1 is used by default") @DefaultValue("1") @QueryParam("depth") int depth,
            @ApiParam(value = "include children files (in addition to children folders). This parameter can be dropped"
                    + ". If not specified ?includeFiles=false is used by default") @DefaultValue("false") @QueryParam("includeFiles") boolean includeFiles)
            throws NotFoundException, ForbiddenException, ServerException {
        final FolderEntry folder = projectManager.asFolder(path);

        if (folder == null) {
            throw new NotFoundException("Folder " + path + " was not found");
        }

        return newDto(TreeElement.class).withNode(injectFolderLinks(asDto(folder)))
                .withChildren(getTree(folder, depth, includeFiles));
    }

    @GET
    @Path("/item/{path:.*}")
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation(value = "Get file or folder", response = ItemReference.class)
    @ApiResponses({ @ApiResponse(code = 200, message = "OK"),
            @ApiResponse(code = 403, message = "User not authorized to call this operation"),
            @ApiResponse(code = 404, message = "Not found"),
            @ApiResponse(code = 500, message = "Internal Server Error") })
    public ItemReference getItem(
            @ApiParam(value = "Path to resource. Can be project or its folders", required = true) @PathParam("path") String path)
            throws NotFoundException, ForbiddenException, ServerException {
        final VirtualFileEntry entry = projectManager.getProjectsRoot().getChild(path);

        if (entry == null) {
            throw new NotFoundException("Project " + path + " was not found");
        }

        if (entry.isFile()) {
            return injectFileLinks(asDto((FileEntry) entry));
        } else {
            return injectFolderLinks(asDto((FolderEntry) entry));
        }
    }

    @GET
    @Path("/search/{path:.*}")
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation(value = "Search for resources", notes = "Search for resources applying a number of search filters as query parameters", response = ItemReference.class, responseContainer = "List")
    @ApiResponses({ @ApiResponse(code = 200, message = "OK"),
            @ApiResponse(code = 403, message = "User not authorized to call this operation"),
            @ApiResponse(code = 404, message = "Not found"), @ApiResponse(code = 409, message = "Conflict error"),
            @ApiResponse(code = 500, message = "Internal Server Error") })
    public List<ItemReference> search(
            @ApiParam(value = "Path to resource, i.e. where to search?", required = true) @PathParam("path") String path,
            @ApiParam(value = "Resource name") @QueryParam("name") String name,
            @ApiParam(value = "Search keywords") @QueryParam("text") String text,
            @ApiParam(value = "Maximum items to display. If this parameter is dropped, there are no limits") @QueryParam("maxItems") @DefaultValue("-1") int maxItems,
            @ApiParam(value = "Skip count") @QueryParam("skipCount") int skipCount)
            throws NotFoundException, ForbiddenException, ConflictException, ServerException {
        final Searcher searcher;
        try {
            searcher = projectManager.getSearcher();
        } catch (NotFoundException e) {
            LOG.warn(e.getLocalizedMessage());
            return Collections.emptyList();
        }

        if (skipCount < 0) {
            throw new ConflictException(String.format("Invalid 'skipCount' parameter: %d.", skipCount));
        }

        final QueryExpression expr = new QueryExpression().setPath(path.startsWith("/") ? path : ('/' + path))
                .setName(name).setText(text).setMaxItems(maxItems).setSkipCount(skipCount);

        final SearchResult result = searcher.search(expr);
        final List<SearchResultEntry> searchResultEntries = result.getResults();
        final List<ItemReference> items = new ArrayList<>(searchResultEntries.size());
        final FolderEntry root = projectManager.getProjectsRoot();

        for (SearchResultEntry searchResultEntry : searchResultEntries) {
            final VirtualFileEntry child = root.getChild(searchResultEntry.getFilePath());

            if (child != null && child.isFile()) {
                items.add(injectFileLinks(asDto((FileEntry) child)));
            }
        }

        return items;
    }

    private void logProjectCreatedEvent(@NotNull String projectName, @NotNull String projectType) {
        LOG.info("EVENT#project-created# PROJECT#{}# TYPE#{}# WS#{}# USER#{}# PAAS#default#", projectName,
                projectType, workspace, EnvironmentContext.getCurrent().getSubject().getUserId());
    }

    private VirtualFileEntry getVirtualFile(String path, boolean force)
            throws ServerException, ForbiddenException, ConflictException, NotFoundException {
        final VirtualFileEntry virtualFile = projectManager.getProjectsRoot().getChild(path);
        if (virtualFile != null && virtualFile.isFile()) {
            // File with same name exist already exists.
            throw new ConflictException(String.format("File with the name '%s' already exists.", path));
        } else {
            if (virtualFile == null) {
                return projectManager.getProjectsRoot().createFolder(path);
            } else if (!force) {
                // Project already exists.
                throw new ConflictException(String.format("Project with the name '%s' already exists.", path));
            }
        }

        return virtualFile;
    }

    private List<TreeElement> getTree(FolderEntry folder, int depth, boolean includeFiles)
            throws ServerException, NotFoundException {
        if (depth == 0) {
            return null;
        }

        final List<? extends VirtualFileEntry> children;

        if (includeFiles) {
            children = folder.getChildFoldersFiles();
        } else {
            children = folder.getChildFolders();
        }

        final List<TreeElement> nodes = new ArrayList<>(children.size());
        for (VirtualFileEntry child : children) {
            if (child.isFolder()) {
                nodes.add(newDto(TreeElement.class).withNode(injectFolderLinks(asDto((FolderEntry) child)))
                        .withChildren(getTree((FolderEntry) child, depth - 1, includeFiles)));
            } else {
                nodes.add(newDto(TreeElement.class).withNode(injectFileLinks(asDto((FileEntry) child))));
            }
        }

        return nodes;
    }

    /* --------------------------------------------------------------------------- */
    /* TODO check "upload" methods below, they were copied from old VFS as is      */
    /* --------------------------------------------------------------------------- */

    private static Response uploadFile(VirtualFile parent, Iterator<FileItem> formData)
            throws ForbiddenException, ConflictException, ServerException {
        try {
            FileItem contentItem = null;
            String name = null;
            boolean overwrite = false;

            while (formData.hasNext()) {
                FileItem item = formData.next();
                if (!item.isFormField()) {
                    if (contentItem == null) {
                        contentItem = item;
                    } else {
                        throw new ServerException("More then one upload file is found but only one should be. ");
                    }
                } else if ("name".equals(item.getFieldName())) {
                    name = item.getString().trim();
                } else if ("overwrite".equals(item.getFieldName())) {
                    overwrite = Boolean.parseBoolean(item.getString().trim());
                }
            }

            if (contentItem == null) {
                throw new ServerException("Cannot find file for upload. ");
            }
            if (name == null || name.isEmpty()) {
                name = contentItem.getName();
            }

            try {
                try {
                    parent.createFile(name, contentItem.getInputStream());
                } catch (ConflictException e) {
                    if (!overwrite) {
                        throw new ConflictException("Unable upload file. Item with the same name exists. ");
                    }
                    parent.getChild(org.eclipse.che.api.vfs.Path.of(name))
                            .updateContent(contentItem.getInputStream(), null);
                }
            } catch (IOException ioe) {
                throw new ServerException(ioe.getMessage(), ioe);
            }

            return Response.ok("", MediaType.TEXT_HTML).build();
        } catch (ForbiddenException | ConflictException | ServerException e) {
            HtmlErrorFormatter.sendErrorAsHTML(e);
            // never thrown
            throw e;
        }
    }

    private static Response uploadZip(VirtualFile parent, Iterator<FileItem> formData)
            throws ForbiddenException, ConflictException, ServerException {
        try {
            FileItem contentItem = null;
            boolean overwrite = false;
            boolean skipFirstLevel = false;
            while (formData.hasNext()) {
                FileItem item = formData.next();
                if (!item.isFormField()) {
                    if (contentItem == null) {
                        contentItem = item;
                    } else {
                        throw new ServerException("More then one upload file is found but only one should be. ");
                    }
                } else if ("overwrite".equals(item.getFieldName())) {
                    overwrite = Boolean.parseBoolean(item.getString().trim());
                } else if ("skipFirstLevel".equals(item.getFieldName())) {
                    skipFirstLevel = Boolean.parseBoolean(item.getString().trim());
                }
            }
            if (contentItem == null) {
                throw new ServerException("Cannot find file for upload. ");
            }
            try {
                importZip(parent, contentItem.getInputStream(), overwrite, skipFirstLevel);
            } catch (IOException ioe) {
                throw new ServerException(ioe.getMessage(), ioe);
            }
            return Response.ok("", MediaType.TEXT_HTML).build();
        } catch (ForbiddenException | ConflictException | ServerException e) {
            HtmlErrorFormatter.sendErrorAsHTML(e);
            // never thrown
            throw e;
        }
    }

    private static void importZip(VirtualFile parent, InputStream in, boolean overwrite, boolean skipFirstLevel)
            throws ForbiddenException, ConflictException, ServerException {
        int stripNum = skipFirstLevel ? 1 : 0;
        parent.unzip(in, overwrite, stripNum);
    }

    private ItemReference injectFileLinks(ItemReference itemReference) {
        return projectServiceLinksInjector.injectFileLinks(itemReference, getServiceContext());
    }

    private ItemReference injectFolderLinks(ItemReference itemReference) {
        return projectServiceLinksInjector.injectFolderLinks(itemReference, getServiceContext());
    }

    private ProjectConfigDto injectProjectLinks(ProjectConfigDto projectConfig) {
        return projectServiceLinksInjector.injectProjectLinks(projectConfig, getServiceContext());
    }
}