/*
* The MIT License
*
* Copyright 2012 Sony Ericsson Mobile Communications. All rights reserved.
* Copyright 2012 Sony Mobile Communications AB. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.sonyericsson.jenkins.plugins.bfa.model;
import com.sonyericsson.jenkins.plugins.bfa.FailureCauseMatrixAggregator;
import com.sonyericsson.jenkins.plugins.bfa.PluginImpl;
import com.sonyericsson.jenkins.plugins.bfa.utils.OldDataConverter;
import hudson.matrix.Combination;
import hudson.matrix.MatrixBuild;
import hudson.matrix.MatrixConfiguration;
import hudson.matrix.MatrixProject;
import hudson.matrix.MatrixRun;
import hudson.model.BuildBadgeAction;
import hudson.model.Cause;
import hudson.model.TopLevelItem;
import jenkins.model.Jenkins;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Build action for the aggregated result of failure causes.
*
* @author Tomas Westling <thomas.westling@sonyericsson.com>
*/
public class FailureCauseMatrixBuildAction implements BuildBadgeAction {
private static final Logger logger = Logger.getLogger(FailureCauseMatrixBuildAction.class.getName());
private transient List<MatrixRun> runs;
private List<String> runIds;
private MatrixBuild build;
/**
* Standard constructor.
*
* @param build the build where this action is placed.
* @param runs the list of MatrixRuns for this action.
*/
public FailureCauseMatrixBuildAction(MatrixBuild build, List<MatrixRun> runs) {
this.build = build;
this.runs = runs;
makeIdList(runs);
}
/**
* Generates the contents of {@link #runIds} so it contains some type of identifiable to later retrieve the runs
* when this is read from disk.
*
* @param matrixRuns the list of runs to get the ids from.
*/
private void makeIdList(List<MatrixRun> matrixRuns) {
logger.finer("making runIds");
this.runIds = new LinkedList<String>();
for (MatrixRun run : matrixRuns) {
MatrixConfiguration configuration = run.getProject();
runIds.add(configuration.getCombination().toString());
}
logger.log(Level.FINER, "runIds size: {0}", runIds.size());
}
@Override
public String getIconFileName() {
return null;
}
@Override
public String getDisplayName() {
return null;
}
@Override
public String getUrlName() {
return null;
}
/**
* Convenience method for getting the action for a specific run.
*
* @param run the run to get the action for.
* @return the FailureCauseBuildAction.
*/
public FailureCauseBuildAction getActionForBuild(MatrixRun run) {
return run.getAction(FailureCauseBuildAction.class);
}
/**
* Gets all the matrix runs that have the failure cause build action.
*
* @return the runs with the action.
*/
public List<MatrixRun> getRunsWithAction() {
List<MatrixRun> returnList = new LinkedList<MatrixRun>();
for (MatrixRun run : getRuns()) {
if (run.getAction(FailureCauseBuildAction.class) != null) {
returnList.add(run);
}
}
return returnList;
}
/**
* Gets the {@link #runs}, if they haven't been loaded yet they will.
*
* @return the runs.
*/
private synchronized List<MatrixRun> getRuns() {
if (runIds != null && build != null && runs == null) {
runs = new LinkedList<MatrixRun>();
for (String id : runIds) {
Combination combination = Combination.fromString(id);
if (combination != null) {
MatrixRun run = build.getRun(combination);
if (run != null) {
runs.add(run);
}
}
}
}
return runs;
}
/**
* Finds the first run with the first identified cause. Null if there are none.
*
* @return the first cause found.
*/
public FoundFailureCause getFirstFailureCause() {
for (MatrixRun run : getRuns()) {
FailureCauseBuildAction action = run.getAction(FailureCauseBuildAction.class);
if (action != null) {
List<FoundFailureCause> foundFailureCauses = action.getFoundFailureCauses();
if (foundFailureCauses != null && !foundFailureCauses.isEmpty()) {
return foundFailureCauses.get(0);
}
}
}
return null;
}
/**
* Gets the image url for the summary page.
*
* @return the image url.
*/
public String getImageUrl() {
return PluginImpl.getFullImageUrl("48x48", PluginImpl.DEFAULT_ICON_NAME);
}
/**
* Gets the image url for the badge page.
*
* @return the image url.
*/
public String getBadgeImageUrl() {
return PluginImpl.getFullImageUrl("16x16", PluginImpl.DEFAULT_ICON_NAME);
}
/**
* Gets the failure causes for a specific matrix run.
*
* @param run the run to find failure causes for.
* @return the failure causes of the run.
*/
public List<FoundFailureCause> getFoundFailureCauses(MatrixRun run) {
FailureCauseBuildAction action = run.getAction(FailureCauseBuildAction.class);
if (action != null) {
return action.getFoundFailureCauses();
}
return new LinkedList<FoundFailureCause>();
}
/**
* Gets the failure causes for a specific matrix run.
*
* @param run the run to find failure causes for.
* @return the failure causes of the run.
*/
public static FailureCauseDisplayData getFailureCauseDisplayData(MatrixRun run) {
FailureCauseBuildAction action = run.getAction(FailureCauseBuildAction.class);
if (action != null) {
return action.getFailureCauseDisplayData();
}
return new FailureCauseDisplayData();
}
/**
* Signal that this object is de-serialized. Will start by checking if {@link #runs} should be converted, otherwise
* check if {@link #runIds} should be converted to {@link #runs}.
*
* @return this object.
*/
public Object readResolve() {
if (needsConvertOld()) {
String project = findUpStreamName();
if (project != null) {
logger.log(Level.FINE, "Scheduling a build in {0} for conversion.", project);
OldDataConverter.getInstance().convertMatrixBuildAction(project, this);
} else {
logger.warning("A MatrixProject's failure cause action needs to be converted,"
+ " but the project name could not be discovered.");
}
}
return this;
}
/**
* Check to see if {@link #convertOldData()} is needed.
*
* @return true if so.
*/
public synchronized boolean needsConvertOld() {
return
(runs != null && (runIds == null || runIds.isEmpty())) //Old data not having runIds
|| (build == null); //Need to find build
}
/**
* Converts from the use of the buggy {@link #runs} to use {@link #runIds}. Only does the conversion if needed.
*/
public synchronized void convertOldData() {
List<MatrixRun> newRuns = null;
if (runs != null && (runIds == null || runIds.isEmpty())) {
logger.fine("Starting conversion");
//old and bad stuff lets do our best to convert.
for (MatrixRun run : runs) {
if (build == null) {
build = findUpStream(run);
logger.log(Level.FINEST, "Build is {0}", build);
}
if (build != null) {
logger.finer("Found a build.");
newRuns = FailureCauseMatrixAggregator.getRuns(build);
makeIdList(newRuns);
break;
}
}
}
if (newRuns != null) {
logger.finer("Setting runs");
runs = newRuns;
}
logger.exiting("FailureCauseMatrixBuildAction", "convertOldData");
}
/**
* Finds the name of the matrix project that this action probably belongs to.
* @return the name of the project or null if runs are bad.
*/
public synchronized String findUpStreamName() {
if (runs != null) {
for (MatrixRun run : runs) {
Cause.UpstreamCause cause = run.getCause(Cause.UpstreamCause.class);
if (cause != null) {
return cause.getUpstreamProject();
}
}
}
return null;
}
/**
* Helper method for {@link #readResolve()}, will try to find the upstream {@link MatrixBuild}. Since the run is
* de-serialized badly (not referenced in the correct hierarchy) this could be a bit tricky.
*
* @param run the MatrixRun to search.
* @return the correct instance if possible.
*/
private MatrixBuild findUpStream(MatrixRun run) {
Cause.UpstreamCause cause = run.getCause(Cause.UpstreamCause.class);
if (cause != null) {
String project = cause.getUpstreamProject();
//Yes the folders plugin could cause problems here,
// but stapler can't help me if I use the URL which would be preferred.
//TODO what to do when the job is renamed?
TopLevelItem item;
item = Jenkins.getInstance().getItem(project);
logger.log(Level.FINE, "Project item for {0} is {1}", new Object[]{project, item});
if (item != null && item instanceof MatrixProject) {
logger.log(Level.FINEST, "It is a matrix project; searching for build {0}", cause.getUpstreamBuild());
//Find the build
return ((MatrixProject)item).getBuildByNumber(cause.getUpstreamBuild());
}
}
return null;
}
}