/*******************************************************************************
* Copyright (c) 2013, 2014 IBM Corporation and others.
* 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package com.ibm.team.build.internal.hjplugin.rtc;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.logging.Logger;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubMonitor;
import com.ibm.team.build.client.ClientFactory;
import com.ibm.team.build.client.ITeamBuildClient;
import com.ibm.team.build.client.ITeamBuildRequestClient;
import com.ibm.team.build.common.BuildItemFactory;
import com.ibm.team.build.common.ScmConstants;
import com.ibm.team.build.common.TeamBuildException;
import com.ibm.team.build.common.TeamBuildStateException;
import com.ibm.team.build.common.model.BuildState;
import com.ibm.team.build.common.model.BuildStatus;
import com.ibm.team.build.common.model.IBuildAction;
import com.ibm.team.build.common.model.IBuildConfigurationElement;
import com.ibm.team.build.common.model.IBuildDefinition;
import com.ibm.team.build.common.model.IBuildEngine;
import com.ibm.team.build.common.model.IBuildEngineHandle;
import com.ibm.team.build.common.model.IBuildProperty;
import com.ibm.team.build.common.model.IBuildRequest;
import com.ibm.team.build.common.model.IBuildRequestHandle;
import com.ibm.team.build.common.model.IBuildRequestParams;
import com.ibm.team.build.common.model.IBuildResult;
import com.ibm.team.build.common.model.IBuildResultContribution;
import com.ibm.team.build.common.model.IBuildResultHandle;
import com.ibm.team.build.common.model.query.IBaseBuildEngineQueryModel.IBuildEngineQueryModel;
import com.ibm.team.build.internal.common.builddefinition.IJazzScmConfigurationElement;
import com.ibm.team.build.internal.common.schedule.IBuildScheduleTaskProperties;
import com.ibm.team.repository.client.IItemManager;
import com.ibm.team.repository.client.ITeamRepository;
import com.ibm.team.repository.common.IContributor;
import com.ibm.team.repository.common.IContributorHandle;
import com.ibm.team.repository.common.ItemNotFoundException;
import com.ibm.team.repository.common.StaleDataException;
import com.ibm.team.repository.common.TeamRepositoryException;
import com.ibm.team.repository.common.UUID;
import com.ibm.team.repository.common.query.IItemQuery;
import com.ibm.team.repository.common.query.IItemQueryPage;
import com.ibm.team.scm.common.IBaselineSet;
import com.ibm.team.scm.common.IWorkspace;
import com.ibm.team.scm.common.IWorkspaceHandle;
public class BuildConnection {
public static final int OK = 0;
public static final int UNSTABLE = 1;
public static final int ERROR = 2;
private static final Logger LOGGER = Logger.getLogger(BuildConnection.class.getName());
/**
* The engine element ID. Constant from HudsonConfigurationElement
*/
public static final String HJ_ENGINE_ELEMENT_ID = "com.ibm.rational.connector.hudson.engine"; //$NON-NLS-1$
/**
* The definition's element ID. Constant from HudsonConfigurationElement (not available in the toolkit)
*/
public static final String HJ_ELEMENT_ID = "com.ibm.rational.connector.hudson"; //$NON-NLS-1$
/**
* The Hudson URL. Constant from HudsonConfigurationElement
*/
public static final String PROPERTY_HUDSON_URL = "com.ibm.rational.connector.hudson.url"; //$NON-NLS-1$
private static final String SLASH = "/"; //$NON-NLS-1$
private static final int MAX_RETRIES = 5;
private static final int RETRY_DELAY = 100; // in milliseconds
final private ITeamRepository fTeamRepository;
private ITeamBuildClient fTeamBuildClient;
private ITeamBuildRequestClient fTeamBuildRequestClient;
public BuildConnection(ITeamRepository repository) {
this.fTeamRepository = repository;
}
/**
* Create a build result to report back the build progress in RTC
* @param buildDefinitionId The id of the build definition. There must be a build definition and it
* will need to have an active build engine associated with it.
* @param personalBuildWorkspace Override the build workspace in the build definition with a personal workspace
* @param buildLabel The label to assign to the build
* @param listener A log to report progress and failures to.
* @param progress A progress monitor to check for cancellation with (and mark progress).
* @param clientLocale The locale of the requesting client
* @return The build result to update with progress of the Jenkins build. May be <code>null</code>
* if there is no build engine associated with the build definition
* @throws TeamRepositoryException Thrown if problems are encountered
* @throws RTCConfigurationException Thrown if the build definition is not valid
* @throws InterruptedException Propagated since it is likely the build getting cancelled.
*/
public IBuildResultHandle createBuildResult(String buildDefinitionId,
IWorkspaceHandle personalBuildWorkspace, String buildLabel,
IConsoleOutput listener, IProgressMonitor progress, Locale clientLocale)
throws TeamRepositoryException, RTCConfigurationException, InterruptedException {
SubMonitor monitor = SubMonitor.convert(progress, 100);
IBuildDefinition buildDefinition = getBuildDefinition(buildDefinitionId, monitor.newChild(20));
if (buildDefinition == null) {
throw new RTCConfigurationException(Messages.get(clientLocale).BuildConnection_build_definition_not_found(buildDefinitionId));
}
List<IBuildProperty> modifiedProperties = new ArrayList<IBuildProperty>(1);
boolean isPersonal = false;
if (personalBuildWorkspace != null) {
isPersonal = true;
IBuildProperty originalProperty = buildDefinition.getProperty(IJazzScmConfigurationElement.PROPERTY_WORKSPACE_UUID);
if (originalProperty != null && !originalProperty.getValue().equals(personalBuildWorkspace.getItemId().getUuidValue())) {
modifiedProperties.add(BuildItemFactory.createBuildProperty(IJazzScmConfigurationElement.PROPERTY_WORKSPACE_UUID, personalBuildWorkspace.getItemId().getUuidValue()));
}
}
IBuildEngineHandle buildEngine = getBuildEngine(buildDefinition, monitor.newChild(20));
if (buildEngine == null) {
LOGGER.finer("There are no RTC build engines associated with the RTC build definition. The build definition must have a supported active build engine"); //$NON-NLS-1$
LOGGER.finer("Unable to create a build result for the build"); //$NON-NLS-1$
// TODO? just use the build definition as is and don't publish the results to RTC?
// Right now we expect build result or workspace uuids on checkout
throw new RTCConfigurationException(Messages.get(clientLocale).BuildConnection_no_build_engine_for_defn(buildDefinitionId));
}
IBuildRequestParams params = BuildItemFactory.createBuildRequestParams();
params.setBuildDefinition(buildDefinition);
params.getNewOrModifiedBuildProperties().addAll(modifiedProperties);
params.setAllowDuplicateRequests(true);
params.setPersonalBuild(isPersonal);
params.getPotentialHandlers().add(buildEngine);
params.setStartBuild(true);
IBuildRequest buildRequest = getTeamBuildRequestClient().requestBuild(params, monitor.newChild(20));
IBuildResultHandle buildResultHandle = buildRequest.getBuildResult();
IBuildResult buildResult = (IBuildResult) getTeamRepository().itemManager().fetchCompleteItem(
buildResultHandle, IItemManager.REFRESH, monitor.newChild(20));
setBuildResultLabel(buildResult, buildLabel, listener, clientLocale, monitor);
return buildResultHandle;
}
/**
* Set the label on the build result, retrying if stale data occurs (race condition between the server & plugin)
* @param buildResultHandle The build result created/started
* @param buildLabel The label to put on the build
* @param listener The listener to log issues setting the label to.
* @param clientLocale The client's locale.
* @param monitor The monitor to handle cancellation
* @return Returns the build result with the updated label. If we are not able to
* save due to stale data, then this may not be the copy in the repository.
* @throws TeamRepositoryException Thrown if something goes wrong setting the label.
* @throws InterruptedException Propagated since it is likely the build getting cancelled.
*/
private IBuildResult setBuildResultLabel(IBuildResult buildResult,
String buildLabel, IConsoleOutput listener, Locale clientLocale, SubMonitor monitor)
throws TeamRepositoryException, InterruptedException {
if (buildLabel != null) {
int tries = 0;
while(tries <= MAX_RETRIES) {
try {
tries++;
buildResult = (IBuildResult) buildResult.getWorkingCopy();
buildResult.setLabel(buildLabel);
buildResult = getTeamBuildClient().save(buildResult, monitor.newChild(20));
break;
} catch (StaleDataException e) {
// Could be the RTC server that initiated the job also setting the label to queued
// retry
if (tries <= MAX_RETRIES) {
Thread.sleep(RETRY_DELAY);
buildResult = (IBuildResult) getTeamRepository().itemManager().fetchCompleteItem(
buildResult, IItemManager.REFRESH, monitor.newChild(20));
} else {
// Not propagating the stale data. If we can't set the label, log it and continue with the build
listener.log(Messages.get(clientLocale).BuildConnection_set_label_failed(buildLabel));
}
}
}
}
return buildResult;
}
/**
* Mark the RTC build result as started if it is not already started or abandonned.
* @param buildResultInfo The structure to be updated with the build result info.
* @param label The label to place on the build when starting it
* @param listener A log to report progress and failures to.
* @param progress A progress monitor to check for cancellation with (and mark progress).
* @throws TeamRepositoryException Thrown if anything goes wrong.
* @throws InterruptedException Propagated since it is likely the build getting cancelled.
*/
public void startBuild(IBuildResultInfo buildResultInfo, String label, IConsoleOutput listener,
IProgressMonitor progress, Locale clientLocale) throws TeamRepositoryException, InterruptedException {
SubMonitor monitor = SubMonitor.convert(progress, 100);
IBuildResultHandle buildResultHandle = (IBuildResultHandle) IBuildResult.ITEM_TYPE.createItemHandle(UUID.valueOf(buildResultInfo.getBuildResultUUID()), null);
IBuildResult buildResult = (IBuildResult) getTeamRepository().itemManager().fetchCompleteItem(
buildResultHandle, IItemManager.REFRESH, monitor.newChild(10));
// Only start the build if not already started
if (buildResult.getState() == BuildState.NOT_STARTED) {
IBuildRequestHandle buildRequestHandle = getBuildRequest(buildResult);
if (buildRequestHandle == null) {
throw new TeamBuildStateException(Messages.get(clientLocale).BuildConnection_start_missing_build_requester());
}
try {
buildResult = getTeamBuildRequestClient().startBuild(buildRequestHandle, IBuildResult.PROPERTIES_COMPLETE, monitor.newChild(60));
buildResultInfo.setOwnLifeCycle(true);
// Setting label since we now own the lifecycle of the build
buildResult = setBuildResultLabel(buildResult, label, listener, clientLocale, monitor.newChild(10));
} catch (TeamBuildStateException e) {
// some one got in there first and started/canceled/abandoned the build before we could
// start it. So we don't own the lifecycle.
// See if it was cancelled/abandoned
buildResult = (IBuildResult) getTeamRepository().itemManager().fetchCompleteItem(
buildResultHandle, IItemManager.REFRESH, monitor.newChild(10));
if (buildResult.getState() == BuildState.CANCELED ||
buildResult.getState() == BuildState.INCOMPLETE) {
// the build has been cancelled/abandonned
throw new OperationCanceledException();
}
}
} else if (buildResult.getState() == BuildState.CANCELED ||
buildResult.getState() == BuildState.INCOMPLETE) {
// the build has been cancelled/abandonned
throw new OperationCanceledException();
} else {
// someone else started it
buildResultInfo.setOwnLifeCycle(false);
}
// capture info about who started the build etc.
getBuildResultInfo(buildResult, buildResultInfo, listener, clientLocale, monitor.newChild(20));
}
/**
* Mark the build result as terminated
* @param buildResultUUID
* @param aborted Whether the Jenkins build was aborted
* @param buildState Whether the Jenkins build was a success, failure, or unstable
* 0 = success, 1 = unstable, 2 = not success or unstable. We can't create constants
* because the -rtc plugin doesn't expose anything other than the facade.
* @param listener A log to report progress and failures to.
* @param progress A progress monitor to check for cancellation with (and mark progress).
* @throws TeamRepositoryException
*/
public void terminateBuild(String buildResultUUID, boolean aborted, int buildState,
IConsoleOutput listener, IProgressMonitor progress, Locale clientLocale) throws TeamRepositoryException {
SubMonitor monitor = SubMonitor.convert(progress, 100);
IBuildResultHandle buildResultHandle = (IBuildResultHandle) IBuildResult.ITEM_TYPE.createItemHandle(UUID.valueOf(buildResultUUID), null);
IBuildResult buildResult = (IBuildResult) getTeamRepository().itemManager().fetchCompleteItem(
buildResultHandle, IItemManager.REFRESH, monitor.newChild(10));
// Do nothing if the build result is already in a final state.
if (buildResult.getState() == BuildState.CANCELED || buildResult.getState() == BuildState.INCOMPLETE
|| buildResult.getState() == BuildState.COMPLETED) {
return;
}
if (aborted) {
if (buildResult.getState() == BuildState.IN_PROGRESS) {
getTeamBuildRequestClient().makeBuildIncomplete(buildResultHandle, IBuildResult.PROPERTIES_COMPLETE, monitor.newChild(90));
} else {
IBuildRequestHandle buildRequestHandle = getBuildRequest(buildResult);
if (buildRequestHandle != null) {
getTeamBuildRequestClient().cancelPendingRequest(buildRequestHandle, IBuildRequestHandle.PROPERTIES_REQUIRED, monitor.newChild(99));
} else {
throw new TeamBuildStateException(Messages.get(clientLocale).BuildConnection_terminate_missing_build_requester());
}
}
} else {
if (buildResult.getState() == BuildState.NOT_STARTED) {
// The build needs to be started in order to complete it
// we probably failed to start it before, but we are responsible for its lifecycle
IBuildRequestHandle buildRequestHandle = getBuildRequest(buildResult);
if (buildRequestHandle != null) {
buildResult = getTeamBuildRequestClient().startBuild(buildRequestHandle, IBuildResult.PROPERTIES_COMPLETE, monitor.newChild(90));
} else {
throw new TeamBuildStateException(Messages.get(clientLocale).BuildConnection_terminate_missing_build_requester());
}
}
if (buildState == UNSTABLE && BuildStatus.WARNING.isMoreSevere(buildResult.getStatus())) {
buildResult = (IBuildResult) buildResult.getWorkingCopy();
buildResult.setStatus(BuildStatus.WARNING);
getTeamBuildClient().save(buildResult, monitor.newChild(50));
} else if (buildState > UNSTABLE && BuildStatus.ERROR.isMoreSevere(buildResult.getStatus())) {
buildResult = (IBuildResult) buildResult.getWorkingCopy();
buildResult.setStatus(BuildStatus.ERROR);
getTeamBuildClient().save(buildResult, monitor.newChild(50));
}
getTeamBuildRequestClient().makeBuildComplete(buildResultHandle, false, IBuildResult.PROPERTIES_COMPLETE, monitor.newChild(40));
}
}
/**
* Helper method to get a valid build engine a build for the definition
* supplied can be recorded against.
*
* @param buildDefinition The definition of the build that will be started
* @param progress A progress monitor to check for cancellation with (and mark progress).
* @return Handle to a build engine that can run a build with the definition supplied
* @throws TeamRepositoryException Thrown if anything goes wrong
*/
public IBuildEngineHandle getBuildEngine(IBuildDefinition buildDefinition, IProgressMonitor progress) throws TeamRepositoryException {
SubMonitor monitor = SubMonitor.convert(progress, 100);
IBuildEngineQueryModel queryModel = IBuildEngineQueryModel.ROOT;
IItemQuery query = IItemQuery.FACTORY.newInstance(queryModel);
query.filter(queryModel.active()._isTrue()._and(
queryModel.supportedBuildDefinitions().itemId()._eq(query.newUUIDArg())));
query.setResultLimit(1);
IItemQueryPage page = getTeamBuildClient().queryItems(query, new Object[] { buildDefinition.getItemId() }, 1, monitor);
if (page.getResultSize() != 0) {
return (IBuildEngineHandle) page.getItemHandles().get(0);
}
return null;
}
/**
* Tests that the specified build definition is valid.
*
* @param buildDefinitionId ID of the RTC build definition
* @param progress A progress monitor to check for cancellation with (and mark progress).
* @param clientLocale The locale of the requesting client
* @throw Exception if an error occurs
* @throws RTCValidationException
* If a build definition for the build definition id could not
* be found.
* @LongOp
*/
public void testBuildDefinition(String buildDefinitionId, IProgressMonitor progress, Locale clientLocale) throws Exception {
SubMonitor monitor = SubMonitor.convert(progress, 100);
// validate the build definition exists
IBuildDefinition buildDefinition = getBuildDefinition(buildDefinitionId, monitor.newChild(25));
if (buildDefinition == null) {
throw new RTCValidationException(Messages.get(clientLocale).BuildConnection_build_definition_not_found(buildDefinitionId));
}
// validate the build definition has a supporting build engine
IBuildEngineHandle buildEngineHandle = getBuildEngine(buildDefinition, monitor.newChild(25));
if (buildEngineHandle == null) {
throw new RTCValidationException(Messages.get(clientLocale).BuildConnection_build_definition_missing_build_engine());
}
// validate the build definition is a hudson/jenkins build definition
IBuildConfigurationElement hudsonDefinitionBuildConfigurationElement = buildDefinition.getConfigurationElement(HJ_ELEMENT_ID);
if (hudsonDefinitionBuildConfigurationElement == null) {
throw new RTCValidationException(Messages.get(clientLocale).BuildConnection_build_definition_missing_hudson_config());
}
// validate the build definition has a hudson/jenkins build engine
IBuildEngine buildEngine = (IBuildEngine) getTeamRepository().itemManager().fetchPartialItem(
buildEngineHandle, IItemManager.REFRESH,
Arrays.asList(IBuildEngine.PROPERTY_CONFIGURATION_ELEMENTS), monitor.newChild(50));
IBuildConfigurationElement hudsonEngineBuildConfigurationElement = buildEngine.getConfigurationElement(HJ_ENGINE_ELEMENT_ID);
if (hudsonEngineBuildConfigurationElement == null) {
throw new RTCValidationException(Messages.get(clientLocale).BuildConnection_build_definition_missing_build_engine_hudson_config());
}
// validate the build definition as a Jazz Source Control option
IBuildConfigurationElement jazzScmDefinitionConfigurationElement = buildDefinition.getConfigurationElement("com.ibm.team.build.jazzscm"); //$NON-NLS-1$
if (jazzScmDefinitionConfigurationElement == null) {
throw new RTCValidationException(Messages.get(clientLocale).BuildConnection_build_definition_missing_jazz_scm_config());
}
}
/**
* Helper method to get the build definition.
*
* @param buildDefinitionId
* The id of the build definition to retrieve.
* @param progress A progress monitor to check for cancellation with (and mark progress).
* @return The build definition represented by the build definition id, or null
* @throws TeamRepositoryException
* If an error occurred retrieving the build definition.
*/
public IBuildDefinition getBuildDefinition(String buildDefinitionId, IProgressMonitor progress) throws TeamRepositoryException {
return getTeamBuildClient().getBuildDefinition(buildDefinitionId, progress);
}
/**
* Retrieves the team build client associated with the logged in repository.
*
* @return The team build client associated with the logged in repository.
* @throws TeamRepositoryException
* If the client could not be acquired.
*/
protected ITeamBuildClient getTeamBuildClient() {
if (fTeamBuildClient == null) {
fTeamBuildClient = ClientFactory.getTeamBuildClient(getTeamRepository());
}
return fTeamBuildClient;
}
/**
* Retrieves the team build client associated with the logged in repository.
*
* @return The team build client associated with the logged in repository.
* @throws TeamRepositoryException
* If the client could not be acquired.
*/
protected ITeamBuildRequestClient getTeamBuildRequestClient() {
if (fTeamBuildRequestClient == null) {
fTeamBuildRequestClient = ClientFactory.getTeamBuildRequestClient(getTeamRepository());
}
return fTeamBuildRequestClient;
}
private ITeamRepository getTeamRepository() {
return fTeamRepository;
}
/**
* Adds a workspace contribution to the build result.
*
* @param workspace
* The workspace to add.
* @param resultHandle Build result handle.
* If <code>null</code> no contribution will be added.
* @param progress A progress monitor to check for cancellation with (and mark progress).
* @throws TeamRepositoryException
* @throws IllegalArgumentException
*/
public void addWorkspaceContribution(IWorkspace workspace, IBuildResultHandle resultHandle,
IProgressMonitor progress) throws IllegalArgumentException,
TeamRepositoryException {
// if no build result, we are done
if (resultHandle == null) {
return;
}
IBuildResultContribution contribution = BuildItemFactory.createBuildResultContribution();
contribution.setExtendedContributionTypeId(ScmConstants.EXTENDED_DATA_TYPE_ID_BUILD_WORKSPACE);
contribution.setImpactsPrimaryResult(false);
contribution.setLabel(workspace.getName());
contribution.setExtendedContribution(workspace);
getTeamBuildClient().addBuildResultContribution(resultHandle, contribution, progress);
}
/**
* Adds a snapshot contribution to the build result.
*
* @param snapshot
* The snapshot to add.
* @param resultHandle Build result handle.
* If <code>null</code> no contribution will be added.
* @param progress A progress monitor to check for cancellation with (and mark progress).
* @throws TeamRepositoryException
* @throws IllegalArgumentException
*/
public void addSnapshotContribution(IBaselineSet snapshot, IBuildResultHandle resultHandle,
IProgressMonitor progress) throws IllegalArgumentException,
TeamRepositoryException {
// if no build result, we are done
if (resultHandle == null || snapshot == null) {
return;
}
IBuildResultContribution contribution = BuildItemFactory.createBuildResultContribution();
contribution.setExtendedContributionTypeId(ScmConstants.EXTENDED_DATA_TYPE_ID_BUILD_SNAPSHOT);
contribution.setImpactsPrimaryResult(false);
contribution.setLabel(Messages.getDefault().BuildConnection_snapshot_label(snapshot.getName()));
contribution.setExtendedContribution(snapshot);
getTeamBuildClient().addBuildResultContribution(resultHandle, contribution, progress);
}
/**
* Start a build activity with the label in the specified build result.
* Returns a unique id for the newly started activity.
* <p>
* If <code>parentId</code> is set, the new activity is created as a child
* of the existing activity with that id.
* <p>
* If <code>autoComplete</code> is true, then this activity will be
* automatically completed when either the build is completed, this
* activity's parent activity is completed or when the next peer activity is
* started.
* <p>
*
* @param buildResultHandle
* The build result to start the activity in.
* If <code>null</code> no activity will be started.
* @param label
* The label describing the activity.
* @param parentId
* Optional id of a parent activity.
* @param autoComplete
* Whether or not to automatically complete this activity.
* @param progress A progress monitor to check for cancellation with (and mark progress).
* @return The unique id of the new activity.
* @throws TeamBuildStateException
* If the build is not currently in progress.
* @throws TeamBuildException
* If <code>parentId</code> is set but an activity with that
* id is not found or is found but has already completed.
* @throws TeamRepositoryException
* If an error occurs while accessing the repository.
* @throws IllegalArgumentException
* If <code>buildResultHandle</code> or <code>label</code>
* is <code>null</code>, or if <code>label</code> is empty.
*/
public String startBuildActivity(IBuildResultHandle buildResultHandle, String label, String parentId,
boolean autoComplete, IProgressMonitor progress) throws TeamRepositoryException,
IllegalArgumentException {
// if no build result, we are done
if (buildResultHandle == null) {
return ""; //$NON-NLS-1$
}
return getTeamBuildClient().startBuildActivity(buildResultHandle, label, parentId, autoComplete, progress);
}
/**
* Complete the build activity with the specified id in the specified build
* result.
*
* @param buildResultHandle
* The build result to complete the activity in.
* If <code>null</code> no activity will be completed.
* @param id
* The id of the activity to complete.
* @param progress A progress monitor to check for cancellation with (and mark progress).
* @throws TeamRepositoryException
* If an error occurs while accessing the repository.
* @throws IllegalArgumentException
* If <code>buildResultHandle</code> or <code>id</code> is
* <code>null</code> or if <code>id</code> is empty.
*/
public void completeBuildActivity(IBuildResultHandle buildResultHandle,
String activityId, IProgressMonitor progress) throws IllegalArgumentException, TeamRepositoryException {
// if no build result, we are done
if (buildResultHandle == null) {
return;
}
getTeamBuildClient().completeBuildActivity(buildResultHandle, activityId, progress);
}
/**
* Add to the build result, links to H/J artifacts. In particular the project and the build.
* @param buildResultUUID The uuid of the build result that the links are to be added to
* @param rootUrl The root url of the H/J server
* @param projectUrl The relative link to the H/J project
* @param buildUrl The relative link to the H/J build
* @param clientConsole The console to send interesting messages to.
* @param progress A progress monitor to check for cancellation with (and mark progress).
* @throws TeamRepositoryException Thrown if anything goes wrong
*/
public void createBuildLinks(String buildResultUUID, String rootUrl,
String projectUrl, String buildUrl, IConsoleOutput clientConsole,
IProgressMonitor progress) throws TeamRepositoryException {
SubMonitor monitor = SubMonitor.convert(progress, 100);
if (buildResultUUID == null) {
return;
}
IBuildResultHandle resultHandle = (IBuildResultHandle) IBuildResult.ITEM_TYPE.createItemHandle(UUID.valueOf(buildResultUUID), null);
if (rootUrl == null) {
LOGGER.finer("Hudson/Jenkins root url has not been configured. Attempting to use the one specified by the RTC build engine for build " + buildUrl); //$NON-NLS-1$
// get the root url from the build engine
IBuildResult result = (IBuildResult) getTeamRepository()
.itemManager()
.fetchPartialItem(
resultHandle,
IItemManager.REFRESH,
Collections
.singleton(IBuildResult.PROPERTY_BUILD_REQUESTS),
monitor.newChild(5));
Collection<String> requestProperties = Arrays.asList(new String[] { IBuildRequest.PROPERTY_BUILD_ACTION,
IBuildRequest.PROPERTY_HANDLER });
monitor.setWorkRemaining(50 + 10 * result.getBuildRequests().size());
for (IBuildRequestHandle buildRequestHandle : (List<IBuildRequestHandle>) result.getBuildRequests()) {
IBuildRequest buildRequest = (IBuildRequest) getTeamRepository().itemManager().fetchPartialItem(
buildRequestHandle, IItemManager.REFRESH, requestProperties, monitor.newChild(5));
if (buildRequest.getBuildAction().getAction().equals(IBuildAction.REQUEST_BUILD)
&& buildRequest.getHandler() != null) {
IBuildEngine buildEngine = (IBuildEngine) getTeamRepository()
.itemManager()
.fetchPartialItem(
buildRequest.getHandler(),
IItemManager.REFRESH,
Collections
.singleton(IBuildEngine.PROPERTY_CONFIGURATION_ELEMENTS),
monitor.newChild(5));
rootUrl = buildEngine.getConfigurationPropertyValue(HJ_ENGINE_ELEMENT_ID, PROPERTY_HUDSON_URL, null);
break;
}
}
}
if (rootUrl == null) {
clientConsole.log(Messages.getDefault().BuildConnection_missing_root_url());
} else {
if (!rootUrl.endsWith(SLASH)) {
rootUrl = rootUrl + SLASH;
}
addLinkContribution(Messages.getDefault().BuildConnection_hj_job(), rootUrl + projectUrl, resultHandle, monitor.newChild(25));
addLinkContribution(Messages.getDefault().BuildConnection_hj_build(), rootUrl + buildUrl, resultHandle, monitor.newChild(25));
}
}
private void addLinkContribution(String label, String url, IBuildResultHandle resultHandle,
IProgressMonitor monitor) throws TeamRepositoryException {
// if no build result, we are done
if (resultHandle == null) {
return;
}
IBuildResultContribution contribution = BuildItemFactory
.createBuildResultContribution();
contribution.setExtendedContributionTypeId(IBuildResultContribution.LINK_EXTENDED_CONTRIBUTION_ID);
contribution.setLabel(label);
contribution.setExtendedContributionProperty(
IBuildResultContribution.PROPERTY_NAME_URL, url);
getTeamBuildClient().addBuildResultContribution(resultHandle, contribution, monitor);
}
/**
* Provides information contained in the build result/request to the caller
* Initially provides information about the cause of the build (scheduled, personal, etc.).
*
* @param buildResultInfo The structure to be updated with the build result info.
* @param clientConsole The console to put messages
* @param progress Monitor to mark progress on
* @throws TeamRepositoryException Thrown if the information can not be retrieved
*/
private void getBuildResultInfo(IBuildResult buildResult, IBuildResultInfo buildResultInfo,
IConsoleOutput clientConsole, Locale clientLocale, IProgressMonitor progress) {
SubMonitor monitor = SubMonitor.convert(progress, 100);
IItemManager itemManager = getTeamRepository().itemManager();
IBuildRequestHandle buildRequestHandle = getBuildRequest(buildResult);
if (buildRequestHandle != null) {
try {
IBuildRequest buildRequest = (IBuildRequest) itemManager.fetchCompleteItem(buildRequestHandle,
IItemManager.REFRESH, monitor.newChild(10));
boolean isScheduled = isScheduledRequest(buildRequest);
buildResultInfo.setScheduled(isScheduled);
buildResultInfo.setPersonalBuild(buildResult.isPersonalBuild());
IContributorHandle requestor = buildRequest.getInitiatingContributor();
if (requestor != null) {
IContributor contributor;
try {
contributor = (IContributor) itemManager.fetchCompleteItem(requestor, IItemManager.DEFAULT, monitor.newChild(25));
buildResultInfo.setRequestor(contributor.getName());
} catch (TeamRepositoryException e) {
clientConsole.log(Messages.get(clientLocale).BuildConnection_unknown_contributor(e.getMessage()));
}
}
} catch(TeamRepositoryException e) {
clientConsole.log(Messages.get(clientLocale).BuildConnection_unknown_start_reason(e.getMessage()));
}
}
}
/**
* Determines if the build request is a scheduled request.
*
* @param buildRequest
* the request to determine if it's scheduled
* @return <code>true</code> if the build request is a scheduled request.
*/
private boolean isScheduledRequest(IBuildRequest buildRequest) {
return containsProperty(buildRequest, IBuildScheduleTaskProperties.PROPERTY_SCHEDULED_BUILD, "true"); //$NON-NLS-1$
}
private boolean containsProperty(IBuildRequest request, String name, String value) {
Map properties = request.getBuildDefinitionProperties();
if (properties != null) {
String propertyValue = (String) properties.get(name);
if (propertyValue != null && propertyValue.equals(value)) {
return true;
}
}
return false;
}
/**
* Returns the first build request for the given build, or <code>null</code>
* if none.
*/
@SuppressWarnings("unchecked")
private IBuildRequestHandle getBuildRequest(IBuildResult result) {
List<IBuildRequestHandle> requests = result.getBuildRequests();
if (requests.isEmpty()) {
return null;
}
return (IBuildRequestHandle) requests.get(0);
}
/**
* Delete the build result
* @param buildResultUUID UUID of the build result to delete.
* @param clientConsole The console to put messages
* @param progress Monitor to mark progress on
* @param clientLocale The locale of the caller
* @throws TeamRepositoryException
*/
public void deleteBuildResult(String buildResultUUID,
IConsoleOutput clientConsole, IProgressMonitor progress,
Locale clientLocale) throws TeamRepositoryException {
IBuildResultHandle resultHandle = (IBuildResultHandle) IBuildResult.ITEM_TYPE.createItemHandle(UUID.valueOf(buildResultUUID), null);
try {
getTeamBuildClient().delete(resultHandle, progress);
} catch (ItemNotFoundException e) {
// its ok if it has already been deleted.
}
}
}