/*******************************************************************************
* Copyright (c) 2012-2015 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.builder.internal;
import org.eclipse.che.api.builder.BuildStatus;
import org.eclipse.che.api.builder.BuilderException;
import org.eclipse.che.api.builder.dto.BuildRequest;
import org.eclipse.che.api.builder.dto.BuildTaskDescriptor;
import org.eclipse.che.api.builder.dto.BuilderDescriptor;
import org.eclipse.che.api.builder.dto.BuilderState;
import org.eclipse.che.api.builder.dto.DependencyRequest;
import org.eclipse.che.api.builder.dto.ServerState;
import org.eclipse.che.api.core.ConflictException;
import org.eclipse.che.api.core.NotFoundException;
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.rest.annotations.Required;
import org.eclipse.che.api.core.rest.shared.dto.Link;
import org.eclipse.che.api.core.util.ContentTypeGuesser;
import org.eclipse.che.api.core.util.SystemInfo;
import org.eclipse.che.api.project.shared.dto.ItemReference;
import org.eclipse.che.api.vfs.server.util.DeleteOnCloseFileInputStream;
import org.eclipse.che.commons.lang.Size;
import org.eclipse.che.commons.lang.TarUtils;
import org.eclipse.che.commons.lang.ZipUtils;
import org.eclipse.che.dto.server.DtoFactory;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
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.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;
import javax.ws.rs.core.UriBuilder;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
/**
* RESTful interface for Builder.
*
* @author andrew00x
*/
@Path("internal/builder")
public final class SlaveBuilderService extends Service {
@Inject
private BuilderRegistry builders;
/** Get list of available Builders which can be accessible over this SlaveBuilderService. */
@GenerateLink(rel = Constants.LINK_REL_AVAILABLE_BUILDERS)
@GET
@Path("available")
@Produces(MediaType.APPLICATION_JSON)
public List<BuilderDescriptor> availableBuilders() {
final Set<Builder> all = builders.getAll();
final List<BuilderDescriptor> list = new ArrayList<>(all.size());
final DtoFactory dtoFactory = DtoFactory.getInstance();
for (Builder builder : all) {
list.add(dtoFactory.createDto(BuilderDescriptor.class)
.withName(builder.getName())
.withDescription(builder.getDescription())
.withEnvironments(builder.getEnvironments()));
}
return list;
}
@GenerateLink(rel = Constants.LINK_REL_BUILDER_STATE)
@GET
@Path("state")
@Produces(MediaType.APPLICATION_JSON)
public BuilderState getBuilderState(@Required
@Description("Name of the builder")
@QueryParam("builder") String builder) throws Exception {
final Builder myBuilder = getBuilder(builder);
return DtoFactory.getInstance().createDto(BuilderState.class)
.withName(myBuilder.getName())
.withStats(myBuilder.getStats())
.withFreeWorkers(myBuilder.getNumberOfWorkers() - myBuilder.getNumberOfActiveWorkers())
.withServerState(getServerState());
}
@GenerateLink(rel = Constants.LINK_REL_SERVER_STATE)
@GET
@Path("server-state")
@Produces(MediaType.APPLICATION_JSON)
public ServerState getServerState() {
return DtoFactory.getInstance().createDto(ServerState.class)
.withCpuPercentUsage(SystemInfo.cpu())
.withTotalMemory(SystemInfo.totalMemory())
.withFreeMemory(SystemInfo.freeMemory());
}
@GenerateLink(rel = Constants.LINK_REL_BUILD)
@POST
@Path("build")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public BuildTaskDescriptor build(@Description("Parameters for build task in JSON format") BuildRequest request) throws Exception {
final Builder myBuilder = getBuilder(request.getBuilder());
final BuildTask task = myBuilder.perform(request);
return getDescriptor(task, getServiceContext().getServiceUriBuilder()).withBuildStats(myBuilder.getStats(task.getId()));
}
@GenerateLink(rel = Constants.LINK_REL_DEPENDENCIES_ANALYSIS)
@POST
@Path("dependencies")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public BuildTaskDescriptor dependencies(@Description("Parameters for analyze dependencies in JSON format") DependencyRequest request)
throws Exception {
final Builder myBuilder = getBuilder(request.getBuilder());
final BuildTask task = myBuilder.perform(request);
return getDescriptor(task, getServiceContext().getServiceUriBuilder()).withBuildStats(myBuilder.getStats(task.getId()));
}
@GET
@Path("status/{builder}/{id}")
@Produces(MediaType.APPLICATION_JSON)
public BuildTaskDescriptor getStatus(@PathParam("builder") String builder, @PathParam("id") Long id) throws Exception {
final Builder myBuilder = getBuilder(builder);
final BuildTask task = myBuilder.getBuildTask(id);
return getDescriptor(task, getServiceContext().getServiceUriBuilder()).withBuildStats(myBuilder.getStats(id));
}
@GET
@Path("logs/{builder}/{id}")
public Response getLogs(@PathParam("builder") String builder, @PathParam("id") Long id) throws Exception {
final BuildLogger logger = getBuilder(builder).getBuildTask(id).getBuildLogger();
return Response.ok(logger.getReader(), logger.getContentType()).build();
}
@POST
@Path("cancel/{builder}/{id}")
@Produces(MediaType.APPLICATION_JSON)
public BuildTaskDescriptor cancel(@PathParam("builder") String builder, @PathParam("id") Long id) throws Exception {
final Builder myBuilder = getBuilder(builder);
final BuildTask task = myBuilder.getBuildTask(id);
task.cancel();
return getDescriptor(task, getServiceContext().getServiceUriBuilder()).withBuildStats(myBuilder.getStats(task.getId()));
}
@GET
@Path("browse/{builder}/{id}")
@Produces(MediaType.TEXT_HTML)
public Response browseDirectory(@PathParam("builder") String builder,
@PathParam("id") Long id,
@DefaultValue(".") @QueryParam("path") String path) throws Exception {
final Builder myBuilder = getBuilder(builder);
final BuildTask task = myBuilder.getBuildTask(id);
final java.io.File workDir = task.getConfiguration().getWorkDir();
final java.io.File target = new java.io.File(workDir, path);
final java.nio.file.Path workDirPath = workDir.toPath().normalize();
final java.nio.file.Path targetPath = target.toPath().normalize();
if (!(targetPath.startsWith(workDirPath))) {
throw new NotFoundException(String.format("Invalid relative path %s", path));
}
if (target.isDirectory()) {
StreamingOutput response = new StreamingOutput() {
@Override
public void write(OutputStream outputStream) throws IOException, WebApplicationException {
String indentation = " ";
final PrintWriter writer = new PrintWriter(outputStream);
writer.print("<div class='file-browser'>");
final UriBuilder serviceUriBuilder = getServiceContext().getServiceUriBuilder();
writer.print("<ul>\n");
java.io.File parent = target.getParentFile();
java.nio.file.Path parentPath = parent.toPath();
if (!targetPath.equals(workDirPath) && parentPath.startsWith(workDirPath)) {
java.io.File[] parentChildren = parent.listFiles();
while (parentPath.startsWith(workDirPath) && parentChildren != null && parentChildren.length == 1) {
parent = parent.getParentFile();
parentPath = parent.toPath();
parentChildren = parent.listFiles();
}
writer.print(indentation);
final String upHref = serviceUriBuilder.clone().path(SlaveBuilderService.class, "browseDirectory")
.replaceQueryParam("path", workDirPath.relativize(parentPath))
.build(task.getBuilder(), task.getId()).toString();
writer.printf("<li><a href='%s'><span class='file-browser-directory-open'>..</span></a></li>", upHref);
}
final java.io.File[] list = target.listFiles();
if (list != null) {
Arrays.sort(list);
for (java.io.File file : list) {
String name = file.getName();
java.nio.file.Path filePath = workDirPath.relativize(file.toPath()).normalize();
writer.print(indentation);
writer.print("<li>");
if (file.isFile()) {
final String openHref = serviceUriBuilder.clone().path(SlaveBuilderService.class, "viewFile")
.replaceQueryParam("path", filePath)
.build(task.getBuilder(), task.getId()).toString();
writer.printf("<a href='%s'><span class='file-browser-file-open'>%s</span></a>", openHref, name);
writer.print(" ");
final String downloadHref = serviceUriBuilder.clone().path(SlaveBuilderService.class, "downloadFile")
.replaceQueryParam("path", filePath)
.build(task.getBuilder(), task.getId()).toString();
writer.printf("<a href='%s'><span class='file-browser-file-download'>download</span></a>", downloadHref);
writer.print(" ");
writer.printf("Size: %s", Size.toHumanSize(file.length()));
} else if (file.isDirectory()) {
java.io.File[] children = file.listFiles();
// Flatten empty directories
java.io.File singleChildDir = null;
while (children != null && children.length == 1 && children[0].isDirectory()) {
singleChildDir = children[0];
children = singleChildDir.listFiles();
}
if (singleChildDir != null) {
filePath = workDirPath.relativize(singleChildDir.toPath()).normalize();
name = targetPath.relativize(singleChildDir.toPath()).normalize().toString();
}
if (children != null && children.length == 0) {
writer.printf("<span class='file-browser-directory-open'>%s/</span></a>", name);
} else {
final String openHref = serviceUriBuilder.clone().path(SlaveBuilderService.class, "browseDirectory")
.replaceQueryParam("path", filePath)
.build(task.getBuilder(), task.getId()).toString();
writer.printf("<a href='%s'><span class='file-browser-directory-open'>%s/</span></a>", openHref, name);
}
}
writer.print("</li>\n");
}
}
writer.print("</ul>\n");
writer.print("</div>");
writer.flush();
}
};
return Response.status(200).entity(response).type(MediaType.TEXT_HTML).build();
}
throw new NotFoundException(String.format("%s does not exist or is not a folder", path));
}
@GET
@Path("tree/{builder}/{id}")
@Produces(MediaType.APPLICATION_JSON)
public List<ItemReference> listDirectory(@PathParam("builder") String builder,
@PathParam("id") Long id,
@DefaultValue(".") @QueryParam("path") String path) throws Exception {
final Builder myBuilder = getBuilder(builder);
final BuildTask task = myBuilder.getBuildTask(id);
final java.io.File workDir = task.getConfiguration().getWorkDir();
final java.io.File target = new java.io.File(workDir, path);
final java.nio.file.Path workDirPath = workDir.toPath().normalize();
final java.nio.file.Path targetPath = target.toPath().normalize();
if (!(targetPath.startsWith(workDirPath))) {
throw new NotFoundException(String.format("Invalid relative path %s", path));
}
final List<ItemReference> result = new LinkedList<>();
final DtoFactory dtoFactory = DtoFactory.getInstance();
if (target.isDirectory()) {
final UriBuilder serviceUriBuilder = getServiceContext().getServiceUriBuilder();
java.io.File parent = target.getParentFile();
java.nio.file.Path parentPath = parent.toPath();
if (!targetPath.equals(workDirPath) && parentPath.startsWith(workDirPath)) {
java.io.File[] parentChildren = parent.listFiles();
while (parentPath.startsWith(workDirPath) && parentChildren != null && parentChildren.length == 1) {
parent = parent.getParentFile();
parentPath = parent.toPath();
parentChildren = parent.listFiles();
}
final String upHref = serviceUriBuilder.clone().path(SlaveBuilderService.class, "listDirectory")
.replaceQueryParam("path", workDirPath.relativize(parentPath))
.build(task.getBuilder(), task.getId()).toString();
final ItemReference up = dtoFactory.createDto(ItemReference.class).withName("..").withPath("..").withType("folder");
up.getLinks().add(dtoFactory.createDto(Link.class).withRel("children").withHref(upHref).withMethod("GET")
.withProduces(MediaType.APPLICATION_JSON));
result.add(up);
}
final java.io.File[] list = target.listFiles();
if (list != null) {
Arrays.sort(list);
for (java.io.File file : list) {
String name = file.getName();
java.nio.file.Path filePath = workDirPath.relativize(file.toPath()).normalize();
if (file.isFile()) {
final String openHref = serviceUriBuilder.clone().path(SlaveBuilderService.class, "viewFile")
.replaceQueryParam("path", filePath)
.build(task.getBuilder(), task.getId()).toString();
final String downloadHref = serviceUriBuilder.clone().path(SlaveBuilderService.class, "downloadFile")
.replaceQueryParam("path", filePath)
.build(task.getBuilder(), task.getId()).toString();
final ItemReference fileItem =
dtoFactory.createDto(ItemReference.class).withName(name).withPath("/" + filePath.toString())
.withType("file");
final List<Link> links = fileItem.getLinks();
final String contentType = ContentTypeGuesser.guessContentType(file);
links.add(dtoFactory.createDto(Link.class).withRel("view").withHref(openHref).withMethod("GET")
.withProduces(contentType));
links.add(dtoFactory.createDto(Link.class).withRel("download").withHref(downloadHref).withMethod("GET")
.withProduces(contentType));
fileItem.getAttributes().put("size", Size.toHumanSize(file.length()));
result.add(fileItem);
} else if (file.isDirectory()) {
java.io.File[] children = file.listFiles();
// Flatten empty directories
java.io.File singleChildDir = null;
while (children != null && children.length == 1 && children[0].isDirectory()) {
singleChildDir = children[0];
children = singleChildDir.listFiles();
}
if (singleChildDir != null) {
filePath = workDirPath.relativize(singleChildDir.toPath()).normalize();
name = targetPath.relativize(singleChildDir.toPath()).normalize().toString();
}
final ItemReference folderItem =
dtoFactory.createDto(ItemReference.class).withName(name).withPath("/" + filePath.toString())
.withType("folder");
if (children == null || children.length != 0) {
final String childrenHref = serviceUriBuilder.clone().path(SlaveBuilderService.class, "listDirectory")
.replaceQueryParam("path", filePath)
.build(task.getBuilder(), task.getId()).toString();
final List<Link> links = folderItem.getLinks();
links.add(dtoFactory.createDto(Link.class).withRel("children").withHref(childrenHref).withMethod("GET")
.withProduces(MediaType.APPLICATION_JSON));
}
result.add(folderItem);
}
}
}
return result;
}
throw new NotFoundException(String.format("%s does not exist or is not a folder", path));
}
@GET
@Path("download/{builder}/{id}")
public Response downloadFile(@PathParam("builder") String builder,
@PathParam("id") Long id,
@Required @QueryParam("path") String path) throws Exception {
final java.io.File workDir = getBuilder(builder).getBuildTask(id).getConfiguration().getWorkDir();
final java.io.File target = new java.io.File(workDir, path);
if (!(target.toPath().normalize().startsWith(workDir.toPath().normalize()))) {
throw new NotFoundException(String.format("Invalid relative path %s", path));
}
if (target.isFile()) {
return Response.status(200)
.header("Content-Disposition", String.format("attachment; filename=\"%s\"", target.getName()))
.type(ContentTypeGuesser.guessContentType(target))
.entity(target)
.build();
}
throw new NotFoundException(String.format("%s does not exist or is not a file", path));
}
@GET
@Path("download-all/{builder}/{id}")
public Response downloadResultArchive(@PathParam("builder") String builder,
@PathParam("id") Long id,
@DefaultValue("tar") @QueryParam("arch") String arch) throws Exception {
final List<File> results = getBuilder(builder).getBuildTask(id).getResult().getResults();
if (results.isEmpty()) {
throw new NotFoundException("Archive with build result is not available.");
}
File archFile;
if ("tar".equals(arch)) {
archFile = Files.createTempFile(String.format("%s-%d-", builder, id), ".tar").toFile();
TarUtils.tarFiles(archFile, 0, results.toArray(new File[results.size()]));
} else if ("zip".equals(arch)) {
archFile = Files.createTempFile(String.format("%s-%d-", builder, id), ".zip").toFile();
ZipUtils.zipFiles(archFile, results.toArray(new File[results.size()]));
} else {
throw new ConflictException(String.format("Unsupported archive type: %s", arch));
}
return Response.status(200)
.header("Content-Disposition", String.format("attachment; filename=\"%s\"", archFile.getName()))
.type(ContentTypeGuesser.guessContentType(archFile))
.entity(new DeleteOnCloseFileInputStream(archFile))
.build();
}
@GET
@Path("view/{builder}/{id}")
public Response viewFile(@PathParam("builder") String builder,
@PathParam("id") Long id,
@Required @QueryParam("path") String path) throws Exception {
final java.io.File workDir = getBuilder(builder).getBuildTask(id).getConfiguration().getWorkDir();
final java.io.File target = new java.io.File(workDir, path);
if (!(target.toPath().normalize().startsWith(workDir.toPath().normalize()))) {
throw new NotFoundException(String.format("Invalid relative path %s", path));
}
if (target.isFile()) {
return Response.status(200).type(ContentTypeGuesser.guessContentType(target)).entity(target).build();
}
throw new NotFoundException(String.format("%s does not exist or is not a file", path));
}
private Builder getBuilder(String name) throws NotFoundException {
final Builder myBuilder = builders.get(name);
if (myBuilder == null) {
throw new NotFoundException(String.format("Unknown builder %s", name));
}
return myBuilder;
}
private BuildTaskDescriptor getDescriptor(BuildTask task, UriBuilder uriBuilder) throws BuilderException {
final String builder = task.getBuilder();
final Long taskId = task.getId();
final BuildResult result = task.getResult();
final java.nio.file.Path workDirPath = task.getConfiguration().getWorkDir().toPath();
final BuildStatus status = task.isDone()
? (task.isCancelled() ? BuildStatus.CANCELLED
: (result.isSuccessful() ? BuildStatus.SUCCESSFUL : BuildStatus.FAILED))
: (task.isStarted() ? BuildStatus.IN_PROGRESS : BuildStatus.IN_QUEUE);
final List<Link> links = new LinkedList<>();
final DtoFactory dtoFactory = DtoFactory.getInstance();
links.add(dtoFactory.createDto(Link.class)
.withRel(Constants.LINK_REL_GET_STATUS)
.withHref(uriBuilder.clone().path(SlaveBuilderService.class, "getStatus").build(builder, taskId).toString())
.withMethod("GET")
.withProduces(MediaType.APPLICATION_JSON));
if (status == BuildStatus.IN_QUEUE || status == BuildStatus.IN_PROGRESS) {
links.add(dtoFactory.createDto(Link.class)
.withRel(Constants.LINK_REL_CANCEL)
.withHref(uriBuilder.clone().path(SlaveBuilderService.class, "cancel").build(builder, taskId).toString())
.withMethod("POST")
.withProduces(MediaType.APPLICATION_JSON));
}
if (status != BuildStatus.IN_QUEUE) {
links.add(dtoFactory.createDto(Link.class)
.withRel(Constants.LINK_REL_VIEW_LOG)
.withHref(uriBuilder.clone().path(SlaveBuilderService.class, "getLogs").build(builder, taskId).toString())
.withMethod("GET")
.withProduces(task.getBuildLogger().getContentType()));
links.add(dtoFactory.createDto(Link.class)
.withRel(Constants.LINK_REL_BROWSE)
.withHref(uriBuilder.clone().path(SlaveBuilderService.class, "browseDirectory").queryParam("path", "/")
.build(builder, taskId).toString())
.withMethod("GET")
.withProduces(MediaType.TEXT_HTML));
}
if (status == BuildStatus.SUCCESSFUL) {
final List<File> results = result.getResults();
for (java.io.File ru : results) {
if (ru.isFile()) {
String relativePath = workDirPath.relativize(ru.toPath()).toString();
if (SystemInfo.isWindows()) {
relativePath = relativePath.replace("\\", "/");
}
links.add(dtoFactory.createDto(Link.class)
.withRel(Constants.LINK_REL_DOWNLOAD_RESULT)
.withHref(uriBuilder.clone().path(SlaveBuilderService.class, "downloadFile")
.queryParam("path", relativePath).build(builder, taskId).toString())
.withMethod("GET")
.withProduces(ContentTypeGuesser.guessContentType(ru)));
}
}
if (!results.isEmpty()) {
links.add(dtoFactory.createDto(Link.class)
.withRel(Constants.LINK_REL_DOWNLOAD_RESULTS_TARBALL)
.withHref(uriBuilder.clone().path(SlaveBuilderService.class, "downloadResultArchive")
.queryParam("arch", "tar")
.build(builder, taskId).toString())
.withMethod("GET"));
links.add(dtoFactory.createDto(Link.class)
.withRel(Constants.LINK_REL_DOWNLOAD_RESULTS_ZIPBALL)
.withHref(uriBuilder.clone().path(SlaveBuilderService.class, "downloadResultArchive")
.queryParam("arch", "zip")
.build(builder, taskId).toString())
.withMethod("GET"));
}
}
if ((status == BuildStatus.SUCCESSFUL || status == BuildStatus.FAILED) && result.hasBuildReport()) {
final java.io.File br = result.getBuildReport();
String relativePath = workDirPath.relativize(br.toPath()).toString();
if (SystemInfo.isWindows()) {
relativePath = relativePath.replace("\\", "/");
}
if (br.isDirectory()) {
links.add(dtoFactory.createDto(Link.class)
.withRel(Constants.LINK_REL_VIEW_REPORT)
.withHref(uriBuilder.clone().path(SlaveBuilderService.class, "browseDirectory")
.queryParam("path", relativePath)
.build(builder, taskId).toString())
.withMethod("GET")
.withProduces(MediaType.TEXT_HTML));
} else {
links.add(dtoFactory.createDto(Link.class)
.withRel(Constants.LINK_REL_VIEW_REPORT)
.withHref(uriBuilder.clone().path(SlaveBuilderService.class, "viewFile")
.queryParam("path", relativePath)
.build(builder, taskId).toString())
.withMethod("GET")
.withProduces(ContentTypeGuesser.guessContentType(br)));
}
}
return dtoFactory.createDto(BuildTaskDescriptor.class)
.withTaskId(taskId)
.withStatus(status)
.withLinks(links)
.withStartTime(task.getStartTime())
.withEndTime(task.getEndTime())
.withCommandLine(task.getCommandLine().toString());
}
}