/*******************************************************************************
* 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;
import org.eclipse.che.api.builder.dto.BaseBuilderRequest;
import org.eclipse.che.api.builder.dto.BuildTaskDescriptor;
import org.eclipse.che.api.builder.dto.BuilderMetric;
import org.eclipse.che.api.builder.internal.Constants;
import org.eclipse.che.api.core.ApiException;
import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.core.rest.HttpOutputMessage;
import org.eclipse.che.api.core.rest.shared.dto.Link;
import org.eclipse.che.api.core.util.Cancellable;
import org.eclipse.che.dto.server.DtoFactory;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriBuilder;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
/**
* @author andrew00x
*/
public class BuildQueueTask implements Cancellable {
private final Long id;
private final long created;
private final long waitingTimeout;
private final BaseBuilderRequest request;
private final Future<RemoteTask> future;
/* NOTE: don't use directly! Always use getter that makes copy of this UriBuilder. */
private final UriBuilder uriBuilder;
private UriBuilder getUriBuilder() {
return uriBuilder.clone();
}
/* ~~~~ */
private RemoteTask remoteTask;
BuildQueueTask(Long id,
BaseBuilderRequest request,
long waitingTimeout,
Future<RemoteTask> future,
UriBuilder uriBuilder) {
this.id = id;
this.uriBuilder = uriBuilder;
this.waitingTimeout = waitingTimeout;
this.future = future;
this.request = request;
created = System.currentTimeMillis();
}
/**
* Get unique id of this task.
*
* @return unique id of this task
*/
public Long getId() {
return id;
}
public BaseBuilderRequest getRequest() {
return DtoFactory.getInstance().clone(request);
}
/**
* Reports that the task was interrupted.
*
* @return {@code true} if task was interrupted and {@code false} otherwise
*/
public boolean isCancelled() throws IOException, BuilderException {
return future.isCancelled();
}
/**
* Reports that the task is waiting in the BuildQueue.
*
* @return {@code true} if task is waiting and {@code false} if the build process already started on slave-builder
*/
public boolean isWaiting() {
return !future.isDone();
}
/** Get date when this task was created. */
public long getCreationTime() {
return created;
}
public long getWaitingTime() {
if (isWaiting()) {
return System.currentTimeMillis() - created;
}
try {
// waiting time is difference between creation of this task and 'remote' builder task
return getRemoteTask().getCreationTime() - created;
} catch (NotFoundException | BuilderException e) {
return -1;
}
}
/**
* Cancel this task.
*
* @throws Exception
* if other error occurs
*/
@Override
public void cancel() throws Exception {
if (future.isCancelled()) {
return;
}
final RemoteTask task = getRemoteTask();
if (task != null) {
task.cancel();
} else {
future.cancel(true);
}
}
/**
* Get status of this task.
*
* @throws BuilderException
* if an error occurs
*/
public BuildTaskDescriptor getDescriptor() throws BuilderException, NotFoundException {
final DtoFactory dtoFactory = DtoFactory.getInstance();
BuildTaskDescriptor descriptor;
if (isWaiting()) {
final List<Link> links = new ArrayList<>(2);
links.add(dtoFactory.createDto(Link.class)
.withRel(Constants.LINK_REL_GET_STATUS)
.withHref(getUriBuilder().path(BuilderService.class, "getStatus").build(request.getWorkspace(), id)
.toString())
.withMethod("GET")
.withProduces(MediaType.APPLICATION_JSON));
links.add(dtoFactory.createDto(Link.class)
.withRel(Constants.LINK_REL_CANCEL)
.withHref(getUriBuilder().path(BuilderService.class, "cancel").build(request.getWorkspace(), id).toString())
.withMethod("POST")
.withProduces(MediaType.APPLICATION_JSON));
final List<BuilderMetric> buildStats = new ArrayList<>(1);
buildStats.add(dtoFactory.createDto(BuilderMetric.class)
.withName(BuilderMetric.WAITING_TIME_LIMIT)
.withValue(Long.toString(created + waitingTimeout))
.withDescription("Waiting for start limit"));
descriptor = dtoFactory.createDto(BuildTaskDescriptor.class)
.withTaskId(id)
.withStatus(BuildStatus.IN_QUEUE)
.withBuildStats(buildStats)
.withLinks(links)
.withStartTime(-1)
.withEndTime(-1)
.withCreationTime(created);
} else if (future.isCancelled()) {
descriptor = dtoFactory.createDto(BuildTaskDescriptor.class)
.withTaskId(id)
.withStatus(BuildStatus.CANCELLED)
.withCreationTime(created)
.withStartTime(-1)
.withEndTime(-1);
} else {
final BuildTaskDescriptor remote = getRemoteTask().getBuildTaskDescriptor();
descriptor = dtoFactory.clone(remote)
.withTaskId(id)
.withCreationTime(created)
.withLinks(rewriteKnownLinks(remote.getLinks()));
}
return descriptor;
}
private List<Link> rewriteKnownLinks(List<Link> links) {
final List<Link> rewritten = new LinkedList<>();
for (Link link : links) {
if (Constants.LINK_REL_GET_STATUS.equals(link.getRel())) {
final Link copy = DtoFactory.getInstance().clone(link);
copy.setHref(getUriBuilder().path(BuilderService.class, "getStatus").build(request.getWorkspace(), id).toString());
rewritten.add(copy);
} else if (Constants.LINK_REL_CANCEL.equals(link.getRel())) {
final Link copy = DtoFactory.getInstance().clone(link);
copy.setHref(getUriBuilder().path(BuilderService.class, "cancel").build(request.getWorkspace(), id).toString());
rewritten.add(copy);
} else if (Constants.LINK_REL_VIEW_LOG.equals(link.getRel())) {
final Link copy = DtoFactory.getInstance().clone(link);
copy.setHref(getUriBuilder().path(BuilderService.class, "getLogs").build(request.getWorkspace(), id).toString());
rewritten.add(copy);
} else if (Constants.LINK_REL_VIEW_REPORT.equals(link.getRel())) {
final Link copy = DtoFactory.getInstance().clone(link);
copy.setHref(getUriBuilder().path(BuilderService.class, "getReport").build(request.getWorkspace(), id).toString());
rewritten.add(copy);
} else if (Constants.LINK_REL_DOWNLOAD_RESULT.equals(link.getRel())) {
final Link copy = DtoFactory.getInstance().clone(link);
// Special behaviour for download links.
// Download links may be multiple.
// Relative path to download file is in query parameter so copy query string from original URL.
final UriBuilder myUriBuilder = getUriBuilder().path(BuilderService.class, "downloadFile");
copyQueryStringInUriBuilder(copy.getHref(), myUriBuilder);
copy.setHref(myUriBuilder.build(request.getWorkspace(), id).toString());
rewritten.add(copy);
} else if (Constants.LINK_REL_DOWNLOAD_RESULTS_TARBALL.equals(link.getRel()) ||
Constants.LINK_REL_DOWNLOAD_RESULTS_ZIPBALL.equals(link.getRel())) {
final Link copy = DtoFactory.getInstance().clone(link);
final UriBuilder myUriBuilder = getUriBuilder().path(BuilderService.class, "downloadResultArchive");
copyQueryStringInUriBuilder(copy.getHref(), myUriBuilder);
copy.setHref(myUriBuilder.build(request.getWorkspace(), id).toString());
rewritten.add(copy);
}
}
return rewritten;
}
private void copyQueryStringInUriBuilder(String url, UriBuilder uriBuilder) {
final int q = url.indexOf('?');
final String queryString = q > 0 ? url.substring(q + 1) : null;
if (queryString != null) {
uriBuilder.replaceQuery(queryString);
}
}
RemoteTask getRemoteTask() throws NotFoundException, BuilderException {
if (!future.isDone()) {
return null;
}
if (remoteTask == null) {
try {
remoteTask = future.get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
final Throwable cause = e.getCause();
if (cause instanceof Error) {
throw (Error)cause;
} else if (cause instanceof RuntimeException) {
throw (RuntimeException)cause;
} else if (cause instanceof NotFoundException) {
throw (NotFoundException)cause;
} else if (cause instanceof ApiException) {
throw new BuilderException(((ApiException)cause).getServiceError());
} else {
throw new BuilderException(cause.getMessage(), cause);
}
}
}
return remoteTask;
}
@Override
public String toString() {
return "BuildQueueTask{" +
"id=" + id +
", request=" + request +
'}';
}
public void readLogs(HttpOutputMessage output) throws BuilderException, IOException, NotFoundException {
if (isWaiting()) {
// Logs aren't available until build starts
throw new BuilderException("Logs are not available. Task is not started yet.");
}
getRemoteTask().readLogs(output);
}
public void readReport(HttpOutputMessage output) throws BuilderException, IOException, NotFoundException {
if (isWaiting()) {
// Logs aren't available until build starts
throw new BuilderException("Report is not available. Task is not started yet.");
}
getRemoteTask().readReport(output);
}
public void downloadFile(String path, HttpOutputMessage output) throws BuilderException, IOException, NotFoundException {
if (isWaiting()) {
// There is nothing for download until build ends
throw new BuilderException(String.format("File '%s' is not available. Task is not started yet.", path));
}
getRemoteTask().downloadFile(path, output);
}
public void downloadResultArchive(String archType, HttpOutputMessage output) throws BuilderException, IOException, NotFoundException {
if (isWaiting()) {
// There is nothing for download until build ends
throw new BuilderException("Results are not available. Task is not started yet.");
}
getRemoteTask().downloadResultArchive(archType, output);
}
public void readFile(String path, HttpOutputMessage output) throws BuilderException, IOException, NotFoundException {
if (isWaiting()) {
// There is nothing for download until build ends
throw new BuilderException(String.format("File '%s' is not available. Task is not started yet.", path));
}
getRemoteTask().readFile(path, output);
}
public void browseDirectory(String path, HttpOutputMessage output) throws BuilderException, IOException, NotFoundException {
if (isWaiting()) {
// There is nothing for download until build ends
throw new BuilderException(String.format("Directory '%s' is not available. Task is not started yet.", path));
}
getRemoteTask().browseDirectory(path, output);
}
public void listDirectory(String path, HttpOutputMessage output) throws BuilderException, IOException, NotFoundException {
if (isWaiting()) {
// There is nothing for download until build ends
throw new BuilderException(String.format("Directory '%s' is not available. Task is not started yet.", path));
}
getRemoteTask().listDirectory(path, output);
}
}