/*
* Copyright 2014-2015. Adaptive.me.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*
*/
package me.adaptive.che.plugin.server.builder;
import me.adaptive.che.infrastructure.vfs.WorkspaceIdLocalFSMountStrategy;
import me.adaptive.core.data.domain.BuildRequestEntity;
import me.adaptive.core.data.domain.types.BuildRequestStatus;
import me.adaptive.core.data.repo.BuildRequestRepository;
import me.adaptive.infra.client.ApiClient;
import me.adaptive.infra.client.api.BuildRequestBody;
import org.eclipse.che.api.builder.BuildStatus;
import org.eclipse.che.api.builder.BuilderException;
import org.eclipse.che.api.builder.dto.BaseBuilderRequest;
import org.eclipse.che.api.builder.dto.BuildRequest;
import org.eclipse.che.api.builder.internal.*;
import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.core.notification.EventService;
import org.eclipse.che.api.core.util.CommandLine;
import org.eclipse.che.dto.server.DtoFactory;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import java.io.File;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
/**
* This is the class responsible for invoking the build in one of the adaptive builder servers.
*
* @author panthro
* Created by panthro on 26/06/15.
*/
public class AdaptiveBuilder extends Builder {
public static final String BUILDER_NAME = "adaptive";
public static final String PLATFORM_OPTION = "platform";
public static final BuildStatus[] FINISHED_STATUSES = {BuildStatus.CANCELLED, BuildStatus.SUCCESSFUL, BuildStatus.FAILED};
private ApiClient apiClient;
private File buildsRoot;
private String buildLogName;
@Inject
private EventService eventService;
@Inject
@Named("buildRequestRepository")
private BuildRequestRepository buildRequestRepository;
/**
* Default constructor.
*
* @param rootDirectory the directory where we can store data
* @param numberOfWorkers the number of workers
* @param queueSize the size of the queue
* @param cleanBuildResultDelay delay
*/
@Inject
public AdaptiveBuilder(@Named(Constants.BASE_DIRECTORY) java.io.File rootDirectory,
@Named(Constants.NUMBER_OF_WORKERS) int numberOfWorkers,
@Named(Constants.QUEUE_SIZE) int queueSize,
@Named(Constants.KEEP_RESULT_TIME) int cleanBuildResultDelay,
@Named("adaptive.api.client.endpoint") String endpoint,
@Named("adaptive.api.client.token") String token,
@Named("adaptive.build.result.root") String buildsRoot,
@Named("adaptive.build.log.name") String buildLogName,
EventService eventService) {
super(rootDirectory, numberOfWorkers, queueSize, cleanBuildResultDelay, eventService);
apiClient = new ApiClient(endpoint, token);
this.buildsRoot = new File(buildsRoot);
this.buildLogName = buildLogName;
}
@Override
public String getName() {
return BUILDER_NAME;
}
@Override
public String getDescription() {
return "Adaptive Builder";
}
/**
* Builds the {@code BuildResult} accordingly with the result from the given task
*
* @param task the task to build the result
* @param successful a boolean if the task was successful
* @return the {@code BuildResult} representing the result of the build
* @throws BuilderException
*/
@Override
protected BuildResult getTaskResult(FutureBuildTask task, boolean successful) throws BuilderException {
//TODO analyze how che choose which file to display when building from the cli
File resultRoot = getBuildResultRoot(task.getConfiguration().getRequest());
return new BuildResult(successful, getBuildArtifacts(resultRoot), new File(resultRoot, buildLogName));
}
/**
* The build result
*
* @param request the request
* @return the File pointing to the buildResultRoot
*/
public File getBuildResultRoot(BaseBuilderRequest request) {
return getBuildsRoot(request.getWorkspace(), request.getProject(), request.getId());
}
public File getBuildsRoot(String workspaceId, String projectName, Long buildId) {
return new File(buildsRoot, getWorkspaceFolderName(workspaceId)
+ File.separator
+ (projectName.startsWith("/") ? projectName.substring(1) : projectName)
+ File.separator
+ buildId);
}
/**
* We are not using the command line, this is for builds that need a command line.
*
* @param config the build config
* @return should return a command line to prevent NullPointerExceptions but it won't do anything
* @throws BuilderException
*/
@Override
protected CommandLine createCommandLine(BuilderConfiguration config) throws BuilderException {
return new CommandLine("date"); //adding a date to the logs :P
}
/**
* Utility method to get the workspace folder name
*
* @param wsId the workspace id
* @return the folder name
*/
private String getWorkspaceFolderName(String wsId) {
return WorkspaceIdLocalFSMountStrategy.getWorkspaceFolderName(wsId);
}
@Override
protected BuildLogger createBuildLogger(BuilderConfiguration buildConfiguration, File logFile) throws BuilderException {
return new AdaptiveBuilderLogger(buildRequestRepository.findOne(buildConfiguration.getRequest().getId()), this, apiClient, eventService);
}
@Override
public BuilderConfigurationFactory getBuilderConfigurationFactory() {
return new AdaptiveBuilderConfigurationFactory(this);
}
@Override
protected Callable<Boolean> createTaskFor(CommandLine commandLine, BuildLogger logger, long timeout, final BuilderConfiguration configuration) {
return new AdaptiveBuilderTask(configuration);
}
public BuildResult getBuildResult(Long taskId) throws NotFoundException {
BuildRequestEntity entity = buildRequestRepository.findOne(taskId);
if (entity == null) {
throw new NotFoundException("Task id " + taskId + " not found");
}
boolean success = BuildRequestStatus.SUCCESSFUL.equals(entity.getStatus());
return new BuildResult(success, getBuildArtifacts(getBuildsRoot(entity.getWorkspace().getWorkspaceId(), entity.getProjectName(), entity.getId())));
}
private List<File> getBuildArtifacts(File resultRoot) {
File[] resultFiles = resultRoot.listFiles((dir, name) -> !buildLogName.equals(name));
return resultFiles == null ? Collections.emptyList() : Arrays.asList(resultFiles);
}
public String getBuildLogName() {
return buildLogName;
}
@Override
public BuildTask getBuildTask(Long id) throws NotFoundException {
try {
return super.getBuildTask(id);
} catch (NotFoundException e) {
BuildRequestEntity entity = buildRequestRepository.findOne(id);
if (entity == null) {
throw e;
}
return new DelegateBuildTask(entity);
}
}
public class AdaptiveBuilderTask implements Callable<Boolean> {
private BuilderConfiguration configuration;
public AdaptiveBuilderTask(BuilderConfiguration configuration) {
this.configuration = configuration;
}
@Override
public Boolean call() throws Exception {
try {
apiClient.getBuilderApi().build(
configuration.getRequest().getId(),
getWorkspaceFolderName(configuration.getRequest().getWorkspace()),
configuration.getRequest().getProjectDescriptor().getName()
, configuration.getRequest().getOptions().getOrDefault(PLATFORM_OPTION, "android"),
new BuildRequestBody("debug")); //TODO build the correct body based on the configuration & request
} catch (Exception e) {
return false;
}
return true;
}
}
private class DelegateBuildTask implements BuildTask {
private final BuildRequestEntity entity;
public DelegateBuildTask(BuildRequestEntity entity) {
this.entity = entity;
}
@Override
public Long getId() {
return entity.getId();
}
@Override
public CommandLine getCommandLine() {
return new CommandLine("");
}
@Override
public String getBuilder() {
return "adaptive";
}
@Override
public BuildLogger getBuildLogger() {
return new AdaptiveBuilderLogger(entity, AdaptiveBuilder.this, apiClient, eventService);
}
@Override
public boolean isStarted() {
return !BuildRequestStatus.IN_QUEUE.equals(entity.getStatus());
}
@Override
public long getStartTime() {
return entity.getStartTime() == null ? 0 : entity.getStartTime().getTime();
}
@Override
public long getEndTime() {
return entity.getEndTime() == null ? 0 : entity.getEndTime().getTime();
}
@Override
public long getRunningTime() {
if (isStarted()) {
return getEndTime() > 0 ? getEndTime() - getStartTime() : System.currentTimeMillis() - getStartTime();
} else {
return 0;
}
}
@Override
public boolean isDone() {
return Arrays.asList(FINISHED_STATUSES).contains(BuildStatus.valueOf(entity.getStatus().name()));
}
@Override
public boolean isCancelled() {
return BuildRequestStatus.CANCELLED.equals(entity.getStatus());
}
@Override
public void cancel() throws BuilderException {
LoggerFactory.getLogger(DelegateBuildTask.class).warn("Calling cancel from the delegate");
}
@Override
public BuildResult getResult() throws BuilderException {
try {
return getBuildResult(this.getId());
} catch (NotFoundException e) {
throw new BuilderException(e);
}
}
@Override
public BuilderConfiguration getConfiguration() {
BaseBuilderRequest request = DtoFactory.getInstance().createDto(BuildRequest.class)
.withUserId(entity.getRequester().getUserId())
.withWorkspace(entity.getWorkspace().getWorkspaceId())
.withProject(entity.getProjectName())
.withId(entity.getId())
.withBuilder(AdaptiveBuilder.BUILDER_NAME)
.withOptions(entity.getAttributes());
try {
return new AdaptiveBuilderConfigurationFactory(AdaptiveBuilder.this).createBuilderConfiguration(request);
} catch (BuilderException e) {
LoggerFactory.getLogger(DelegateBuildTask.class).error("Error creating configuration", e);
return null;
}
}
}
@Override
public String toString() {
return BUILDER_NAME;
}
@Override
protected void cleanup(BuildTask task) {
//Nope, there's nothing to cleanup
}
}