package com.hubspot.blazar.resources;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.NotFoundException;
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.core.MediaType;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.hubspot.blazar.base.BuildOptions;
import com.hubspot.blazar.base.BuildTrigger;
import com.hubspot.blazar.base.D3GraphData;
import com.hubspot.blazar.base.D3GraphLink;
import com.hubspot.blazar.base.D3GraphNode;
import com.hubspot.blazar.base.DependencyGraph;
import com.hubspot.blazar.base.GitInfo;
import com.hubspot.blazar.base.InterProjectBuild;
import com.hubspot.blazar.base.InterProjectBuildMapping;
import com.hubspot.blazar.base.InterProjectBuildStatus;
import com.hubspot.blazar.base.Module;
import com.hubspot.blazar.base.RepositoryBuild;
import com.hubspot.blazar.data.service.BranchService;
import com.hubspot.blazar.data.service.DependenciesService;
import com.hubspot.blazar.data.service.InterProjectBuildMappingService;
import com.hubspot.blazar.data.service.InterProjectBuildService;
import com.hubspot.blazar.data.service.ModuleService;
import com.hubspot.blazar.data.service.RepositoryBuildService;
@Path("/inter-project-builds")
@Produces(MediaType.APPLICATION_JSON)
public class InterProjectBuildResource {
private final DependenciesService dependenciesService;
private InterProjectBuildService interProjectBuildService;
private InterProjectBuildMappingService interProjectBuildMappingService;
private BranchService branchService;
private final ModuleService moduleService;
private RepositoryBuildService repositoryBuildService;
@Inject
public InterProjectBuildResource(DependenciesService dependenciesService,
InterProjectBuildService interProjectBuildService,
InterProjectBuildMappingService interProjectBuildMappingService,
RepositoryBuildService repositoryBuildService,
BranchService branchService,
ModuleService moduleService) {
this.repositoryBuildService = repositoryBuildService;
this.dependenciesService = dependenciesService;
this.interProjectBuildService = interProjectBuildService;
this.interProjectBuildMappingService = interProjectBuildMappingService;
this.branchService = branchService;
this.moduleService = moduleService;
}
@POST
@Path("/")
public InterProjectBuild triggerWithOptions(BuildOptions buildOptions, @QueryParam("username") Optional<String> username) {
InterProjectBuild build = InterProjectBuild.getQueuedBuild(buildOptions.getModuleIds(), BuildTrigger.forUser(username.or("unknown")));
long id = interProjectBuildService.enqueue(build);
return interProjectBuildService.getWithId(id).get();
}
@GET
@Path("/{id}")
public Optional<InterProjectBuild> getInterProjectBuild(@PathParam("id") long id) {
return interProjectBuildService.getWithId(id);
}
@GET
@Path("/repository-build/{repoBuildId}/up-and-downstreams")
public InterProjectBuildStatus getMappingsForRepoBuild(@PathParam("repoBuildId") long repoBuildId) {
InterProjectBuildStatus empty = new InterProjectBuildStatus(repoBuildId, Optional.<Long>absent(), Optional.<InterProjectBuild.State>absent(), ImmutableMap.<Long, String>of(), ImmutableMap.<Long, String>of(), ImmutableMap.<Long, String>of(), ImmutableMap.<Long, String>of(), ImmutableSet.<Module>of());
Optional<RepositoryBuild> repoBuild = repositoryBuildService.get(repoBuildId);
Set<InterProjectBuildMapping> interProjectBuildMappingsForRepoBuild = interProjectBuildMappingService.getByRepoBuildId(repoBuildId);
if (interProjectBuildMappingsForRepoBuild.isEmpty() || !repoBuild.isPresent()) {
return empty;
}
InterProjectBuildMapping mapping = interProjectBuildMappingsForRepoBuild.iterator().next();
Optional<InterProjectBuild> build = interProjectBuildService.getWithId(mapping.getInterProjectBuildId());
if (!build.isPresent()) {
return empty;
}
Set<InterProjectBuildMapping> mappings = interProjectBuildMappingService.getMappingsForInterProjectBuild(build.get().getId().get());
Set<Integer> rootBuildModuleIds = build.get().getModuleIds();
Set<Integer> downstreamModuleIds = new HashSet<>();
Set<Integer> upstreamModuleIds = new HashSet<>();
Set<Integer> failedModules = new HashSet<>();
// filter out mappings that are from this repo build
Set<InterProjectBuildMapping> toRemove = new HashSet<>();
for (InterProjectBuildMapping m : mappings) {
if (m.getRepoBuildId().isPresent() && m.getRepoBuildId().get().equals(repoBuildId)) {
toRemove.add(m);
Set<Integer> outgoing = build.get().getDependencyGraph().get().outgoingVertices(m.getModuleId());
downstreamModuleIds.addAll(outgoing);
upstreamModuleIds.addAll(build.get().getDependencyGraph().get().incomingVertices(m.getModuleId()));
}
if (m.getState().equals(InterProjectBuild.State.FAILED)) {
failedModules.add(m.getModuleId());
}
}
mappings.removeAll(toRemove);
upstreamModuleIds.removeAll(rootBuildModuleIds);
// find downstream, upstream and cancelled nodes
Map<Long, String> rootRepoBuilds = getRepoBuildIdsFromModuleIds(rootBuildModuleIds, mappings);
Map<Long, String> downstreamRepoBuilds = getRepoBuildIdsFromModuleIds(downstreamModuleIds, mappings);
Map<Long, String> upstreamRepoBuilds = getRepoBuildIdsFromModuleIds(upstreamModuleIds, mappings);
Map<Long, String> failedRepoBuilds = getRepoBuildIdsFromModuleIds(failedModules, mappings);
Set<Module> cancelled = new HashSet<>();
for (InterProjectBuildMapping m : mappings) {
if (m.getState().equals(InterProjectBuild.State.CANCELLED) && downstreamModuleIds.contains(m.getModuleId())) {
cancelled.add(moduleService.get(m.getModuleId()).get());
}
}
return new InterProjectBuildStatus(repoBuildId, build.get().getId(), Optional.of(build.get().getState()), rootRepoBuilds, upstreamRepoBuilds, downstreamRepoBuilds, failedRepoBuilds, cancelled);
}
@GET
@Path("/graph/{id}")
public DependencyGraph dependencyGraph(@PathParam("id") int id) {
return dependenciesService.buildInterProjectDependencyGraph(Sets.newHashSet(moduleService.get(id).get()));
}
@POST
@Path("/cancel/{id}")
public void cancel(@PathParam("id") long interProjectBuildId) {
Optional<InterProjectBuild> build = interProjectBuildService.getWithId(interProjectBuildId);
if (!build.isPresent()) {
throw new NotFoundException("No build found for id: " + interProjectBuildId);
}
interProjectBuildService.cancel(build.get());
}
@GET
@Path("/drawableGraph")
public D3GraphData getDrawableGraph(@QueryParam("moduleId") Set<Integer> moduleIds) {
Set<Module> modules = new HashSet<>();
Map<Integer, InterProjectBuild.State> moduleIdToState = new HashMap<>();
for (int i : moduleIds) {
Optional<Module> m = moduleService.get(i);
if (m.isPresent()) {
modules.add(m.get());
}
}
DependencyGraph graph = dependenciesService.buildInterProjectDependencyGraph(modules);
for (Map.Entry<Integer, Set<Integer>> entry : graph.getTransitiveReduction().entrySet()) {
moduleIdToState.put(entry.getKey(), InterProjectBuild.State.QUEUED);
for (int i : entry.getValue()) {
moduleIdToState.put(i, InterProjectBuild.State.QUEUED);
}
}
List<D3GraphNode> nodes = getNodes(graph, moduleIdToState);
List<D3GraphLink> links = drawLinks(nodes, graph);
return new D3GraphData(links, nodes);
}
@GET
@Path("/drawableGraph/{id}")
public D3GraphData getDrawableGraphForBuild(@PathParam("id") long interProjectBuildId) {
InterProjectBuild build = interProjectBuildService.getWithId(interProjectBuildId).get();
Set<InterProjectBuildMapping> mappings = interProjectBuildMappingService.getMappingsForInterProjectBuild(interProjectBuildId);
Map<Integer, InterProjectBuild.State> moduleIdToState = new HashMap<>();
for (InterProjectBuildMapping mapping : mappings) {
moduleIdToState.put(mapping.getModuleId(), mapping.getState());
}
for (Map.Entry<Integer, Set<Integer>> entry : build.getDependencyGraph().get().getTransitiveReduction().entrySet()) {
if (!moduleIdToState.containsKey(entry.getKey())) {
moduleIdToState.put(entry.getKey(), InterProjectBuild.State.QUEUED);
}
for (int i : entry.getValue()) {
if (!moduleIdToState.containsKey(i)) {
moduleIdToState.put(i, InterProjectBuild.State.QUEUED);
}
}
}
List<D3GraphNode> nodes = getNodes(build.getDependencyGraph().get(), moduleIdToState);
List<D3GraphLink> links = drawLinks(nodes, build.getDependencyGraph().get());
return new D3GraphData(links, nodes);
}
private List<D3GraphNode> getNodes(DependencyGraph graph, Map<Integer, InterProjectBuild.State> moduleIdToState) {
Set<Module> modules = new HashSet<>();
for (int i: graph.getTopologicalSort()) {
Optional<Module> m = moduleService.get(i);
if (m.isPresent()) {
modules.add(m.get());
}
}
List<D3GraphNode> nodes = new ArrayList<>();
for (Module module : modules) {
GitInfo gitInfo = branchService.get(moduleService.getBranchIdFromModuleId(module.getId().get())).get();
String source = String.format("%s-%s", gitInfo.getRepository(), module.getName());
D3GraphNode node = new D3GraphNode(source, module.getId().get(), 100, 100, moduleIdToState.get(module.getId().get()));
nodes.add(node);
}
return nodes;
}
private List<D3GraphLink> drawLinks(List<D3GraphNode> nodes, DependencyGraph graph) {
List<D3GraphLink> links = new ArrayList<>();
int pos = 0;
for (D3GraphNode node : nodes) {
Set<Integer> outgoingModules = graph.outgoingVertices(node.getModuleId());
for (int module : outgoingModules) {
links.add(new D3GraphLink(getPos(nodes, module), pos));
}
pos++;
}
return links;
}
private int getPos(List<D3GraphNode> nodes, int moduleId) {
int pos = 0;
for (D3GraphNode node : nodes) {
if (node.getModuleId() == moduleId) {
return pos;
}
pos++;
}
throw new IllegalStateException(String.format("No node with moduleId %d", moduleId));
}
private Map<Long, String> getRepoBuildIdsFromModuleIds(Set<Integer> moduleIds, Set<InterProjectBuildMapping> mappings) {
Map<Long, String> repoBuildIds = new HashMap<>();
for (int i : moduleIds) {
for (InterProjectBuildMapping m : mappings) {
if (m.getModuleId() == i && m.getRepoBuildId().isPresent()) {
GitInfo branch = branchService.get(m.getBranchId()).get();
String name = String.format("%s-%s-%s-%s",
branch.getHost(),
branch.getOrganization(),
branch.getRepository(),
branch.getBranch());
repoBuildIds.put(m.getRepoBuildId().get(), name);
}
}
}
return repoBuildIds;
}
}