gt:     FolderEntry root = projectManager.getProjectsRoot();
context: /*
   * Copyright (c) 2012-2017 Red Hat, Inc.
   * 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:
   *   Red Hat, Inc. - initial API and implementation
   */
  package org.eclipse.che.api.project.server;
  
  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;
  
  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 java.io.File;
  import java.io.IOException;
  import java.io.InputStream;
  import java.net.URI;
  import java.util.ArrayList;
  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 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 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.jsonrpc.commons.JsonRpcException;
  import org.eclipse.che.api.core.jsonrpc.commons.RequestHandlerConfigurator;
  import org.eclipse.che.api.core.jsonrpc.commons.RequestTransmitter;
  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.core.util.CompositeLineConsumer;
  import org.eclipse.che.api.project.server.importer.ProjectImportOutputJsonRpcLineConsumer;
  import org.eclipse.che.api.project.server.importer.ProjectImportOutputJsonRpcRegistrar;
  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.ProjectSearchRequestDto;
  import org.eclipse.che.api.project.shared.dto.ProjectSearchResponseDto;
  import org.eclipse.che.api.project.shared.dto.SearchOccurrenceDto;
  import org.eclipse.che.api.project.shared.dto.SearchResultDto;
  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.vfs.search.impl.LuceneSearcher;
  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;
  
  /**
   * 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 Tika TIKA;
  
    private final ProjectManager projectManager;
    private final EventService eventService;
    private final ProjectServiceLinksInjector projectServiceLinksInjector;
    private final ProjectServiceVcsStatusInjector vcsStatusInjector;
    private final RequestTransmitter transmitter;
    private final ProjectImportOutputJsonRpcRegistrar projectImportHandlerRegistrar;
    private final String workspace;
  
    @Inject
    public ProjectService(
        ProjectManager projectManager,
        EventService eventService,
        ProjectServiceLinksInjector projectServiceLinksInjector,
        ProjectServiceVcsStatusInjector vcsStatusInjector,
        RequestTransmitter transmitter,
        ProjectImportOutputJsonRpcRegistrar projectImportHandlerRegistrar) {
      this.projectManager = projectManager;
      this.eventService = eventService;
      this.projectServiceLinksInjector = projectServiceLinksInjector;
      this.vcsStatusInjector = vcsStatusInjector;
      this.transmitter = transmitter;
      this.projectImportHandlerRegistrar = projectImportHandlerRegistrar;
      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 {
  
      final int delayBetweenMessages = 300;
  
      final ProjectImportOutputWSLineConsumer wsLineConsumer =
          new ProjectImportOutputWSLineConsumer(path, delayBetweenMessages);
  
      final ProjectImportOutputJsonRpcLineConsumer rpcLineConsumer =
          new ProjectImportOutputJsonRpcLineConsumer(
              path, transmitter, projectImportHandlerRegistrar, delayBetweenMessages);
  
      projectManager.importProject(
          path,
          sourceStorage,
          force,
          () -> new CompositeLineConsumer(wsLineConsumer, rpcLineConsumer));
    }
  
    @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(vcsStatusInjector.injectVcsStatus(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(getTIKA().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(), getTIKA().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 vcsStatusInjector.injectVcsStatus(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(vcsStatusInjector.injectVcsStatus(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 = SearchResult.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 ProjectSearchResponseDto 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 DtoFactory.newDto(ProjectSearchResponseDto.class);
      }
  
      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)
              .setIncludePositions(true);
  
      final SearchResult result = searcher.search(expr);
      final List<SearchResultEntry> searchResultEntries = result.getResults();
      return DtoFactory.newDto(ProjectSearchResponseDto.class)
          .withTotalHits(result.getTotalHits())
          .withItemReferences(prepareResults(searchResultEntries));
    }
  
    /**
     * Prepare result for client, add additional information like line number and line content where
     * found given text
     *
     * @throws ServerException
     */
    private List<SearchResultDto> prepareResults(List<SearchResultEntry> searchResultEntries)
        throws ServerException {
      List<SearchResultDto> results = new ArrayList<>(searchResultEntries.size());
  