diff --git a/pom.xml b/pom.xml
index c24b10c5..0e65494c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2,7 +2,7 @@
4.0.0
com.sonyericsson.jenkins.plugins.bfa
build-failure-analyzer
- 1.19.3-SNAPSHOT
+ 1.20.0
hpi
Build Failure Analyzer
Jenkins Build Failure Analyzer Plugin
@@ -245,6 +245,11 @@
1.35
test
+
+ com.amazonaws
+ aws-java-sdk
+ 1.11.106
+
diff --git a/src/main/java/com/sonyericsson/jenkins/plugins/bfa/db/DynamoDBKnowledgeBase.java b/src/main/java/com/sonyericsson/jenkins/plugins/bfa/db/DynamoDBKnowledgeBase.java
new file mode 100644
index 00000000..fd7f6507
--- /dev/null
+++ b/src/main/java/com/sonyericsson/jenkins/plugins/bfa/db/DynamoDBKnowledgeBase.java
@@ -0,0 +1,534 @@
+package com.sonyericsson.jenkins.plugins.bfa.db;
+
+import com.amazonaws.regions.Region;
+import com.amazonaws.regions.RegionUtils;
+import com.amazonaws.regions.Regions;
+import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper;
+import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBScanExpression;
+import com.amazonaws.services.dynamodbv2.model.Condition;
+import com.amazonaws.services.dynamodbv2.model.CreateTableRequest;
+import com.amazonaws.services.dynamodbv2.model.ProvisionedThroughput;
+import com.amazonaws.services.dynamodbv2.util.TableUtils;
+import com.sonyericsson.jenkins.plugins.bfa.model.FailureCause;
+import com.sonyericsson.jenkins.plugins.bfa.Messages;
+import com.sonyericsson.jenkins.plugins.bfa.statistics.Statistics;
+import hudson.Extension;
+import hudson.model.Descriptor;
+import com.amazonaws.AmazonClientException;
+import com.amazonaws.auth.profile.ProfileCredentialsProvider;
+import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
+import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
+import hudson.util.FormValidation;
+import hudson.util.ListBoxModel;
+import jenkins.model.Jenkins;
+import org.kohsuke.stapler.DataBoundConstructor;
+import org.kohsuke.stapler.QueryParameter;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Handling of the Amazon Web Services DynamoDB way of saving the knowledge base.
+ *
+ * @author Ken Petti <kpetti@constantcontact.com>
+ */
+public class DynamoDBKnowledgeBase extends KnowledgeBase {
+
+ private static final long serialVersionUID = 1;
+ private static final String DYNAMODB_DEFAULT_REGION = Regions.DEFAULT_REGION.getName();
+ private static final String DYNAMODB_DEFAULT_CREDENTIALS_PATH =
+ System.getProperty("user.home") + "/.aws/credentials";
+ private static final String DYNAMODB_DEFAULT_CREDENTIAL_PROFILE = "default";
+ static final Map NOT_REMOVED_FILTER_EXPRESSION = new HashMap(){{
+ put("_removed", new Condition().withComparisonOperator("NULL"));
+ }};
+
+ private static AmazonDynamoDB dynamoDB;
+ private transient DynamoDBMapper dbMapper;
+
+ private String region;
+ private String credentialsPath;
+ private String credentialsProfile;
+
+ /**
+ * Getter for the DynamoDB region.
+ * @return the region.
+ */
+ public String getRegion() {
+ return region;
+ }
+
+ /**
+ * Getter for the AWS credentials path.
+ * @return the credentialsPath.
+ */
+ public String getCredentialsPath() {
+ return credentialsPath;
+ }
+
+ /**
+ * Getter for the AWS credentials profile.
+ * @return the credentialsProfile string.
+ */
+ public String getCredentialsProfile() {
+ return credentialsProfile;
+ }
+
+ /**
+ * Standard constructor.
+ * @param region the AWS region to connect to DynamoDB with.
+ * @param credentialsPath the path to a local file containing AWS credentials.
+ * @param credentialsProfile the AWS credential profile to use for connecting to DynamoDB.
+ */
+ @DataBoundConstructor
+ public DynamoDBKnowledgeBase(String region, String credentialsPath, String credentialsProfile) {
+ if (region == null || region.isEmpty()) {
+ region = DYNAMODB_DEFAULT_REGION;
+ }
+ if (credentialsPath == null || credentialsPath.isEmpty()) {
+ credentialsPath = DYNAMODB_DEFAULT_CREDENTIALS_PATH;
+ }
+ if (credentialsProfile == null || credentialsProfile.isEmpty()) {
+ credentialsProfile = DYNAMODB_DEFAULT_CREDENTIAL_PROFILE;
+ }
+
+ this.region = region;
+ this.credentialsPath = credentialsPath;
+ this.credentialsProfile = credentialsProfile;
+ }
+
+ /**
+ * Get the list of {@link FailureCause}s except those marked as removed. It is intended to be used in the scanning
+ * phase hence it should be returned as quickly as possible, so the list could be cached.
+ *
+ * @return the full list of causes.
+ */
+ // TODO: 4/19/18 Add caching here
+ @Override
+ public Collection getCauses() {
+ DynamoDBScanExpression scan = new DynamoDBScanExpression();
+ scan.setScanFilter(NOT_REMOVED_FILTER_EXPRESSION);
+ return getDbMapper().scan(FailureCause.class, scan);
+ }
+
+ /**
+ * Get the list of the {@link FailureCause}'s names and ids. The list should be the latest possible from the DB as
+ * they will be used for editing. The objects returned should contain at least the id and the name of the cause.
+ *
+ * @return the full list of the names and ids of the causes.
+ */
+ @Override
+ public Collection getCauseNames() {
+ DynamoDBScanExpression scan = new DynamoDBScanExpression();
+ scan.addExpressionAttributeNamesEntry("#n", "name");
+ scan.setProjectionExpression("id,#n");
+ scan.setScanFilter(NOT_REMOVED_FILTER_EXPRESSION);
+ return getDbMapper().scan(FailureCause.class, scan);
+ }
+
+ /**
+ * Get a shallow list of the {@link FailureCause}s. The list should be the latest possible from the DB as
+ * they will be used in the list of causes to edit.
+ * shallow meaning no indications but information enough to show a nice list; at least id and name but description,
+ * comment, lastOccurred and categories are preferred as well.
+ *
+ * @return a shallow list of all causes.
+ * @see #getCauseNames()
+ */
+ @Override
+ public Collection getShallowCauses() {
+ DynamoDBScanExpression scan = new DynamoDBScanExpression();
+ // The following attributes are reserved words in Dynamo, so we need to substitute the actual name for
+ // something safe
+ scan.addExpressionAttributeNamesEntry("#n", "name");
+ scan.addExpressionAttributeNamesEntry("#c", "comment");
+ scan.addExpressionAttributeNamesEntry("#r", "_removed");
+ scan.setProjectionExpression("id,#n,description,categories,#c,modifications,lastOccurred");
+ scan.setFilterExpression(" attribute_not_exists(#r) ");
+ return getDbMapper().scan(FailureCause.class, scan);
+ }
+
+ /**
+ * Get the cause with the given id. The cause returned is intended to be edited right away, so it should be as fresh
+ * from the db as possible.
+ *
+ * @param id the id of the cause.
+ * @return the cause or null if a cause with that id could not be found.
+ */
+ @Override
+ public FailureCause getCause(String id) {
+ return getDbMapper().load(FailureCause.class, id);
+ }
+
+ /**
+ * Saves a new cause to the db, which generates a new id for the cause.
+ *
+ * @param cause the cause to add.
+ * @return the same cause but with a new id.
+ */
+ @Override
+ public FailureCause addCause(FailureCause cause) {
+ return saveCause(cause);
+ }
+
+ /**
+ * Marks the cause as removed in the knowledge base.
+ *
+ * @param id the id of the cause to remove.
+ * @return the removed FailureCause.
+ */
+ @Override
+ public FailureCause removeCause(String id) {
+ FailureCause cause = getDbMapper().load(FailureCause.class, id);
+ cause.setRemoved();
+ getDbMapper().save(cause);
+ return cause;
+ }
+
+ /**
+ * Saves a cause to the db. Assumes that the id is kept from when it was fetched. Can also be an existing cause in
+ * another {@link KnowledgeBase} implementation with a preexisting id that is being converted via {@link
+ * #convertFrom(KnowledgeBase)}.
+ *
+ * @param cause the cause to add.
+ * @return the same cause but with a new id.
+ */
+ @Override
+ public FailureCause saveCause(FailureCause cause) {
+ getDbMapper().save(cause);
+ return cause;
+ }
+
+ /**
+ * Converts the existing old knowledge base into this one. Will be called after the creation of a new object when
+ * then Jenkins config is saved, So it could just be that the old one is exactly the same as this one.
+ *
+ * @param oldKnowledgeBase the old one.
+ * @throws Exception if converting DB fails or something in the KnowledgeBase handling goes wrong.
+ */
+ @Override
+ public void convertFrom(KnowledgeBase oldKnowledgeBase) throws Exception {
+ if (oldKnowledgeBase instanceof DynamoDBKnowledgeBase) {
+ convertFromAbstract(oldKnowledgeBase);
+ convertRemoved((DynamoDBKnowledgeBase)oldKnowledgeBase);
+ } else {
+ for (FailureCause cause : oldKnowledgeBase.getCauseNames()) {
+ saveCause(cause);
+ }
+ }
+ }
+
+ /**
+ * Copies all causes flagged as removed from the old database to this one.
+ *
+ * @param oldKnowledgeBase the old database.
+ */
+ private void convertRemoved(DynamoDBKnowledgeBase oldKnowledgeBase) {
+ Collection removed = oldKnowledgeBase.getRemovedCauses();
+ for (FailureCause obj : removed) {
+ saveCause(obj);
+ }
+ }
+
+ /**
+ * Gets all causes flagged as removed in a "raw" JSON format.
+ *
+ * @return the list of removed causes.
+ */
+ private Collection getRemovedCauses() {
+ DynamoDBScanExpression scan = new DynamoDBScanExpression();
+ scan.setFilterExpression(" attribute_exists(#r) ");
+ return getDbMapper().scan(FailureCause.class, scan);
+ }
+
+ /**
+ * Gets the unique categories of all FailureCauses.
+ *
+ * @return the list of categories.
+ */
+ @Override
+ public List getCategories() {
+ DynamoDBScanExpression scan = new DynamoDBScanExpression();
+ scan.setProjectionExpression("categories");
+ scan.setFilterExpression(" attribute_exists(categories) ");
+ List causes = getDbMapper().scan(FailureCause.class, scan);
+ Set categories = new HashSet<>();
+ for (FailureCause c:causes) {
+ categories.addAll(c.getCategories());
+ }
+ return new ArrayList<>(categories);
+ }
+
+ /**
+ * Called to see if the configuration has changed.
+ *
+ * @param oldKnowledgeBase the previous config.
+ * @return true if it is the same.
+ */
+ @Override
+ public boolean equals(KnowledgeBase oldKnowledgeBase) {
+ if (getClass().isInstance(oldKnowledgeBase)) {
+ DynamoDBKnowledgeBase oldDynamoDBKnowledgeBase = (DynamoDBKnowledgeBase)oldKnowledgeBase;
+ return oldDynamoDBKnowledgeBase.getRegion().equals(region)
+ && oldDynamoDBKnowledgeBase.getCredentialsPath().equals(credentialsPath)
+ && oldDynamoDBKnowledgeBase.getCredentialsProfile().equals(credentialsProfile);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Overrides base Object equals.
+ * @param other object to check
+ * @return boolean if values are equal
+ */
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof KnowledgeBase) {
+ return this.equals((KnowledgeBase)other);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Makes checkstyle happy.
+ * @return hashcode of class
+ */
+ @Override
+ public int hashCode() {
+ //Making checkstyle happy.
+ return getClass().getName().hashCode();
+ }
+
+ /**
+ * Called when the KnowledgeBase should be up and running.
+ */
+ @Override
+ public void start() {
+ getDynamoDb();
+ }
+
+ /**
+ * Called when it is time to clean up after the KnowledgeBase.
+ */
+ // TODO: 4/19/18 Implement this
+ @Override
+ public void stop() {
+
+ }
+
+ /**
+ * If Statistics logging is enabled on this knowledge base or not.
+ *
+ * @return true if so. False if not or not implemented.
+ */
+ // TODO: 4/19/18 Implement this
+ @Override
+ public boolean isStatisticsEnabled() {
+ return false;
+ }
+
+ /**
+ * If all builds should be added to statistics logging, not just unsuccessful builds.
+ * Only relevant if {@link #isStatisticsEnabled()} is true.
+ *
+ * @return true if set, false otherwise or if not implemented
+ */
+ // TODO: 4/19/18 Implement this
+ @Override
+ public boolean isSuccessfulLoggingEnabled() {
+ return false;
+ }
+
+ /**
+ * Saves the Statistics.
+ *
+ * @param stat the Statistics.
+ * @throws Exception if something in the KnowledgeBase handling goes wrong.
+ */
+ // TODO: 4/19/18 Implement this
+ @Override
+ public void saveStatistics(Statistics stat) throws Exception {
+
+ }
+
+ /**
+ * Get an instance of {@link AmazonDynamoDB}. Connects to the defined region with the defined AWS
+ * credentials file/profile. If this has been called before, it will return the cached version.
+ * @return instance of AmazonDynamoDB
+ */
+ private AmazonDynamoDB getDynamoDb() {
+ if (dynamoDB != null) {
+ return dynamoDB;
+ }
+
+ ProfileCredentialsProvider credentialsProvider =
+ new ProfileCredentialsProvider(credentialsPath, credentialsProfile);
+ try {
+ credentialsProvider.getCredentials();
+ } catch (Exception e) {
+ throw new AmazonClientException(
+ "Cannot load the credentials from the credential profiles file. "
+ + "Please make sure that your credentials file is at the correct "
+ + "location (~/.aws/credentials), and is in valid format.",
+ e);
+ }
+
+ dynamoDB = AmazonDynamoDBClientBuilder.standard()
+ .withCredentials(credentialsProvider)
+ .withRegion(region)
+ .build();
+
+ return dynamoDB;
+ }
+
+ /**
+ * Get a cached or new instance of {@link DynamoDBMapper}.
+ * @return dbMapper
+ */
+ private DynamoDBMapper getDbMapper() {
+ if (dbMapper != null) {
+ return dbMapper;
+ }
+ dbMapper = new DynamoDBMapper(getDynamoDb());
+ createTable(dbMapper.generateCreateTableRequest(FailureCause.class));
+
+ return dbMapper;
+ }
+
+ /**
+ * Creates a DynamoDB table.
+ * @param request {@link CreateTableRequest}
+ */
+ private void createTable(CreateTableRequest request) {
+ try {
+ String tableName = request.getTableName();
+ AmazonDynamoDB db = getDynamoDb();
+ request.setProvisionedThroughput(new ProvisionedThroughput()
+ .withReadCapacityUnits(1L).withWriteCapacityUnits(1L));
+ TableUtils.createTableIfNotExists(db, request);
+ TableUtils.waitUntilActive(db, tableName);
+
+ } catch (Exception e) {
+ throw new AmazonClientException(e);
+ }
+ }
+
+ /**
+ * Use Jenkins to get and instance of {@link DynamoDBKnowledgeBaseDescriptor}.
+ * @return Descriptor
+ */
+ @Override
+ public Descriptor getDescriptor() {
+ return Jenkins.getInstance().getDescriptorByType(DynamoDBKnowledgeBaseDescriptor.class);
+ }
+
+ /**
+ * Descriptor for {@link DynamoDBKnowledgeBase}.
+ */
+ @Extension
+ public static class DynamoDBKnowledgeBaseDescriptor extends KnowledgeBaseDescriptor {
+
+ @Override
+ public String getDisplayName() {
+ return Messages.DynamoDBKnowledgeBase_DisplayName();
+ }
+
+ /**
+ * Convenience method for jelly.
+ * @return the default region.
+ */
+ public String getDefaultRegion() {
+ return DYNAMODB_DEFAULT_REGION;
+ }
+
+ /**
+ * Convenience method for jelly.
+ * @return the default region.
+ */
+ public String getDefaultCredentialsPath() {
+ return DYNAMODB_DEFAULT_CREDENTIALS_PATH;
+ }
+
+ /**
+ * Convenience method for jelly.
+ * @return the default region.
+ */
+ public String getDefaultCredentialProfile() {
+ return DYNAMODB_DEFAULT_CREDENTIAL_PROFILE;
+ }
+
+ /**
+ * Get a list of valid AWS regions for Jelly.
+ * @return ListBoxModel containing AWS regions
+ */
+ public ListBoxModel doFillRegionItems() {
+ ListBoxModel items = new ListBoxModel();
+ for (Region r:RegionUtils.getRegions()) {
+ String regionName = r.getName();
+ items.add(regionName, regionName);
+ }
+ return items;
+ }
+
+ /**
+ * Checks that the credential file exists.
+ *
+ * @param value the pattern to check.
+ * @return {@link hudson.util.FormValidation#ok()} if everything is well.
+ */
+ public FormValidation doCheckCredentialsPath(@QueryParameter("value") final String value) {
+ File f = new File(value);
+ if(!f.exists()) {
+ return FormValidation.error("Credential file does not exist!");
+ }
+
+ if (f.isDirectory()) {
+ return FormValidation.error("Credential file can not be a directory!");
+ }
+ return FormValidation.ok();
+ }
+
+ /**
+ * Checks that the credential profile is set.
+ *
+ * @param value the pattern to check.
+ * @return {@link hudson.util.FormValidation#ok()} if everything is well.
+ */
+ public FormValidation doCheckCredentialsProfile(@QueryParameter("value") final String value) {
+ if (value == null || value.isEmpty()) {
+ return FormValidation.warning("No credential profile entered, using \"default\" profile");
+ }
+
+ return FormValidation.ok();
+ }
+
+ /**
+ * Tests if the provided parameters can connect to the DynamoDB service.
+ * @param region the region name.
+ * @param credentialsPath the filepath to credentials.
+ * @param credentialProfile the credential profile to use.
+ * @return {@link FormValidation#ok() } if can be done,
+ * {@link FormValidation#error(java.lang.String) } otherwise.
+ */
+ public FormValidation doTestConnection(
+ @QueryParameter("region") final String region,
+ @QueryParameter("credentialsPath") final String credentialsPath,
+ @QueryParameter("credentialProfile") final String credentialProfile
+ ) {
+ DynamoDBKnowledgeBase base = new DynamoDBKnowledgeBase(region, credentialsPath, credentialProfile);
+ try {
+ base.getDynamoDb();
+ } catch (Exception e) {
+ return FormValidation.error(e, Messages.DynamoDBKnowledgeBase_ConnectionError());
+ }
+ return FormValidation.ok(Messages.DynamoDBKnowledgeBase_ConnectionOK());
+ }
+ }
+}
diff --git a/src/main/java/com/sonyericsson/jenkins/plugins/bfa/model/FailureCause.java b/src/main/java/com/sonyericsson/jenkins/plugins/bfa/model/FailureCause.java
index a9b36edc..8585c4f0 100644
--- a/src/main/java/com/sonyericsson/jenkins/plugins/bfa/model/FailureCause.java
+++ b/src/main/java/com/sonyericsson/jenkins/plugins/bfa/model/FailureCause.java
@@ -23,6 +23,15 @@
*/
package com.sonyericsson.jenkins.plugins.bfa.model;
+import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute;
+import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey;
+import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable;
+import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAutoGeneratedKey;
+import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTypeConverted;
+import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBIgnore;
+import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTypeConverter;
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.codehaus.jackson.type.TypeReference;
import com.sonyericsson.jenkins.plugins.bfa.CauseManagement;
import com.sonyericsson.jenkins.plugins.bfa.PluginImpl;
import com.sonyericsson.jenkins.plugins.bfa.db.KnowledgeBase;
@@ -47,6 +56,7 @@
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import org.codehaus.jackson.annotate.JsonIgnoreType;
import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.ObjectMapper;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.Stapler;
@@ -54,10 +64,17 @@
import org.kohsuke.stapler.StaplerResponse;
import java.io.Serializable;
-import java.util.Arrays;
+import java.lang.reflect.Field;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.List;
import java.util.Date;
import java.util.LinkedList;
-import java.util.List;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TimeZone;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -67,17 +84,30 @@
*
* @author Tomas Westling <thomas.westling@sonyericsson.com>
*/
+@DynamoDBTable(tableName = "failureCauses")
@JsonIgnoreProperties(ignoreUnknown = true)
public class FailureCause implements Serializable, Action, Describable {
private static final Logger logger = Logger.getLogger(FailureCause.class.getName());
+ @DynamoDBHashKey(attributeName = "id")
+ @DynamoDBAutoGeneratedKey
private String id;
+ @DynamoDBAttribute(attributeName = "name")
private String name;
+ @DynamoDBAttribute(attributeName = "description")
private String description;
+ @DynamoDBAttribute(attributeName = "comment")
private String comment;
+ @DynamoDBAttribute(attributeName = "lastOccurred")
private Date lastOccurred;
+ @DynamoDBAttribute(attributeName = "categories")
private List categories;
+ @DynamoDBTypeConverted(converter = IndicationsTypeConverter.class)
+ @DynamoDBAttribute(attributeName = "indications")
private List indications;
+ @DynamoDBAttribute(attributeName = "modifications")
private List modifications;
+ @DynamoDBAttribute(attributeName = "_removed")
+ private Map removed;
/**
* Standard data bound constructor.
@@ -338,8 +368,7 @@ public String getId() {
/**
* The id.
- *
- * @param id the id.
+ * @param id String
*/
@Id
@ObjectId
@@ -356,6 +385,14 @@ public String getName() {
return name;
}
+ /**
+ * Setter for the name.
+ * @param name String
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
/**
* Getter for the description.
*
@@ -365,6 +402,12 @@ public String getDescription() {
return description;
}
+ /**
+ * Setter for the description.
+ * @param description String
+ */
+ public void setDescription(String description) { this.description = description; }
+
/**
* Getter for the comment.
*
@@ -374,6 +417,45 @@ public String getComment() {
return comment;
}
+ /**
+ * Setter for the comment.
+ * @param comment String
+ */
+ public void setComment(String comment) { this.comment = comment; }
+
+ /**
+ * Getter for removed info.
+ *
+ * @return the removed info.
+ */
+ public Map getRemoved() {
+ return removed;
+ }
+
+ /**
+ * Setter for removed. This creates the removed info before setting it
+ */
+ public void setRemoved() {
+ TimeZone tz = TimeZone.getTimeZone("UTC");
+ // Quoted "Z" to indicate UTC, no timezone offset
+ DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'");
+ df.setTimeZone(tz);
+ String nowAsISO = df.format(new Date());
+
+ Map removedInfo = new HashMap<>();
+ removedInfo.put("by", Jenkins.getAuthentication().getName());
+ removedInfo.put("timestamp", nowAsISO);
+ setRemoved(removedInfo);
+ }
+
+ /**
+ * Setter for removed, when removed is defined elsewhere.
+ * @param removedInfo map containing "by" and "timestamp" keys
+ */
+ public void setRemoved(Map removedInfo) {
+ this.removed = removedInfo;
+ }
+
/**
* Getter for the last occurrence.
*
@@ -393,6 +475,7 @@ public Date getLastOccurred() {
* @return the last occurrence.
*/
@JsonIgnore
+ @DynamoDBIgnore
public Date getAndInitiateLastOccurred() {
if (lastOccurred == null && id != null) {
loadLastOccurred();
@@ -427,12 +510,21 @@ public List getModifications() {
return modifications;
}
+ /**
+ * Setter for the list of modifications.
+ * @param modifications List of {@link FailureCauseModification}
+ */
+ public void setModifications(List modifications) {
+ this.modifications = modifications;
+ }
+
/**
* Initiates the list of modifications if it's not already initiated
* and then returns the list.
* @return list of modifications
*/
@JsonIgnore
+ @DynamoDBIgnore
public List getAndInitiateModifications() {
if ((modifications == null || modifications.isEmpty())
&& id != null) {
@@ -456,6 +548,7 @@ public List getCategories() {
* @return the categories as a String.
*/
@JsonIgnore
+ @DynamoDBIgnore
public String getCategoriesAsString() {
if (categories == null || categories.isEmpty()) {
return null;
@@ -515,6 +608,7 @@ private void initModifications() {
* @return the latest modification
*/
@JsonIgnore
+ @DynamoDBIgnore
public FailureCauseModification getLatestModification() {
List mods = getAndInitiateModifications();
if (mods != null && !mods.isEmpty()) {
@@ -566,6 +660,12 @@ public List getIndications() {
return indications;
}
+ /**
+ * Setter for the list of indications.
+ * @param indications List of {@link Indication}
+ */
+ public void setIndications(List indications) { this.indications = indications; }
+
//CS IGNORE JavadocMethod FOR NEXT 8 LINES. REASON: The exception can be thrown.
/**
@@ -575,6 +675,7 @@ public List getIndications() {
* @throws IllegalStateException if no ancestor is found.
*/
@JsonIgnore
+ @DynamoDBIgnore
public CauseManagement getAncestorCauseManagement() {
StaplerRequest currentRequest = Stapler.getCurrentRequest();
if (currentRequest == null) {
@@ -589,28 +690,62 @@ public CauseManagement getAncestorCauseManagement() {
@Override
@JsonIgnore
+ @DynamoDBIgnore
public String getIconFileName() {
return PluginImpl.getDefaultIcon();
}
@Override
@JsonIgnore
+ @DynamoDBIgnore
public String getDisplayName() {
return name;
}
@Override
@JsonIgnore
+ @DynamoDBIgnore
public String getUrlName() {
return id;
}
@Override
+ @DynamoDBIgnore
public FailureCauseDescriptor getDescriptor() {
return Jenkins.getInstance().getDescriptorByType(FailureCauseDescriptor.class);
}
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) { return true; }
+ if (!(o instanceof FailureCause)) {
+ return false;
+ }
+
+ FailureCause fc = (FailureCause)o;
+ Field[] fields = this.getClass().getDeclaredFields();
+ EqualsBuilder eb = new EqualsBuilder();
+ for (Field f:fields) {
+ try {
+ eb.append(f.get(this), f.get(fc));
+ } catch (IllegalArgumentException | IllegalAccessException e) {
+ return false;
+ }
+ }
+ return eb.isEquals();
+ }
+
+ /**
+ * Makes checkstyle happy.
+ * @return hashcode of class
+ */
+ @Override
+ public int hashCode() {
+ //Making checkstyle happy.
+ return getClass().getName().hashCode();
+ }
+
/**
* Descriptor is only used for auto completion of categories.
*/
@@ -688,4 +823,59 @@ public AutoCompletionCandidates doAutoCompleteCategories(@QueryParameter String
return candidates;
}
}
+
+ /**
+ * Defines methods to convert {@link Indication}s to/from DynamoDB objects.
+ */
+ public static class IndicationsTypeConverter implements
+ DynamoDBTypeConverter>, List> {
+ private ObjectMapper objectMapper = new ObjectMapper();
+
+ /**
+ * Transform a list of {@link Indication} objects into a list of maps,
+ * for use as a {@link FailureCause} property.
+ * @param indications list of {@link Indication}s
+ * @return the converted list of {@link Indication}s
+ */
+ @Override
+ public List