/*
* SonarQube
* Copyright (C) 2009-2017 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.db.issue;
import com.google.common.base.Joiner;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableSet;
import com.google.protobuf.InvalidProtocolBufferException;
import java.io.Serializable;
import java.util.Collection;
import java.util.Date;
import java.util.Set;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.sonar.api.resources.Project;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.Duration;
import org.sonar.api.utils.KeyValueFormat;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.util.Uuids;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.protobuf.DbIssues;
import org.sonar.db.rule.RuleDefinitionDto;
import org.sonar.db.rule.RuleDto;
import static com.google.common.base.Preconditions.checkArgument;
import static org.sonar.api.utils.DateUtils.dateToLong;
import static org.sonar.api.utils.DateUtils.longToDate;
public final class IssueDto implements Serializable {
private static final char TAGS_SEPARATOR = ',';
private static final Joiner TAGS_JOINER = Joiner.on(TAGS_SEPARATOR).skipNulls();
private static final Splitter TAGS_SPLITTER = Splitter.on(',').trimResults().omitEmptyStrings();
private Long id;
private int type;
private String kee;
private String componentUuid;
private String projectUuid;
private Integer ruleId;
private String severity;
private boolean manualSeverity;
private String message;
private Integer line;
private Double gap;
private Long effort;
private String status;
private String resolution;
private String checksum;
private String assignee;
private String authorLogin;
private String issueAttributes;
private byte[] locations;
private long createdAt;
private long updatedAt;
// functional dates stored as Long
private Long issueCreationDate;
private Long issueUpdateDate;
private Long issueCloseDate;
/**
* Temporary date used only during scan
*/
private Long selectedAt;
// joins
private String ruleKey;
private String ruleRepo;
private String language;
private String componentKey;
private String moduleUuid;
private String moduleUuidPath;
private String projectKey;
private String filePath;
private String tags;
/**
* On batch side, component keys and uuid are useless
*/
public static IssueDto toDtoForComputationInsert(DefaultIssue issue, int ruleId, long now) {
return new IssueDto()
.setKee(issue.key())
.setType(issue.type())
.setLine(issue.line())
.setLocations((DbIssues.Locations) issue.getLocations())
.setMessage(issue.message())
.setGap(issue.gap())
.setEffort(issue.effortInMinutes())
.setResolution(issue.resolution())
.setStatus(issue.status())
.setSeverity(issue.severity())
.setManualSeverity(issue.manualSeverity())
.setChecksum(issue.checksum())
.setAssignee(issue.assignee())
.setRuleId(ruleId)
.setRuleKey(issue.ruleKey().repository(), issue.ruleKey().rule())
.setTags(issue.tags())
.setComponentUuid(issue.componentUuid())
.setComponentKey(issue.componentKey())
.setModuleUuid(issue.moduleUuid())
.setModuleUuidPath(issue.moduleUuidPath())
.setProjectUuid(issue.projectUuid())
.setProjectKey(issue.projectKey())
.setIssueAttributes(KeyValueFormat.format(issue.attributes()))
.setAuthorLogin(issue.authorLogin())
.setIssueCreationDate(issue.creationDate())
.setIssueCloseDate(issue.closeDate())
.setIssueUpdateDate(issue.updateDate())
.setSelectedAt(issue.selectedAt())
// technical dates
.setCreatedAt(now)
.setUpdatedAt(now);
}
/**
* On server side, we need component keys and uuid
*/
public static IssueDto toDtoForServerInsert(DefaultIssue issue, ComponentDto component, ComponentDto project, int ruleId, long now) {
return toDtoForComputationInsert(issue, ruleId, now)
.setComponent(component)
.setProject(project);
}
public static IssueDto toDtoForUpdate(DefaultIssue issue, long now) {
// Invariant fields, like key and rule, can't be updated
return new IssueDto()
.setKee(issue.key())
.setType(issue.type())
.setLine(issue.line())
.setLocations((DbIssues.Locations) issue.getLocations())
.setMessage(issue.message())
.setGap(issue.gap())
.setEffort(issue.effortInMinutes())
.setResolution(issue.resolution())
.setStatus(issue.status())
.setSeverity(issue.severity())
.setChecksum(issue.checksum())
.setManualSeverity(issue.manualSeverity())
.setAssignee(issue.assignee())
.setIssueAttributes(KeyValueFormat.format(issue.attributes()))
.setAuthorLogin(issue.authorLogin())
.setRuleKey(issue.ruleKey().repository(), issue.ruleKey().rule())
.setTags(issue.tags())
.setComponentUuid(issue.componentUuid())
.setComponentKey(issue.componentKey())
.setModuleUuid(issue.moduleUuid())
.setModuleUuidPath(issue.moduleUuidPath())
.setProjectUuid(issue.projectUuid())
.setProjectKey(issue.projectKey())
.setIssueCreationDate(issue.creationDate())
.setIssueCloseDate(issue.closeDate())
.setIssueUpdateDate(issue.updateDate())
.setSelectedAt(issue.selectedAt())
// technical date
.setUpdatedAt(now);
}
public static IssueDto createFor(Project project, RuleDto rule) {
return new IssueDto()
.setProjectUuid(project.getUuid())
.setRuleId(rule.getId())
.setKee(Uuids.create());
}
public String getKey() {
return getKee();
}
public Long getId() {
return id;
}
public IssueDto setId(@Nullable Long id) {
this.id = id;
return this;
}
public String getKee() {
return kee;
}
public IssueDto setKee(String s) {
this.kee = s;
return this;
}
public IssueDto setComponent(ComponentDto component) {
this.componentKey = component.getKey();
this.componentUuid = component.uuid();
this.moduleUuid = component.moduleUuid();
this.moduleUuidPath = component.moduleUuidPath();
this.filePath = component.path();
return this;
}
public IssueDto setProject(ComponentDto project) {
this.projectKey = project.getKey();
this.projectUuid = project.uuid();
return this;
}
public Integer getRuleId() {
return ruleId;
}
/**
* please use setRule(RuleDto rule)
*/
public IssueDto setRuleId(Integer ruleId) {
this.ruleId = ruleId;
return this;
}
@CheckForNull
public String getSeverity() {
return severity;
}
public IssueDto setSeverity(@Nullable String s) {
checkArgument(s == null || s.length() <= 10, "Value is too long for issue severity: %s", s);
this.severity = s;
return this;
}
public boolean isManualSeverity() {
return manualSeverity;
}
public IssueDto setManualSeverity(boolean manualSeverity) {
this.manualSeverity = manualSeverity;
return this;
}
@CheckForNull
public String getMessage() {
return message;
}
public IssueDto setMessage(@Nullable String s) {
checkArgument(s == null || s.length() <= 4000, "Value is too long for issue message: %s", s);
this.message = s;
return this;
}
@CheckForNull
public Integer getLine() {
return line;
}
public IssueDto setLine(@Nullable Integer i) {
checkArgument(i == null || i >= 0, "Value of issue line must be positive: %d", i);
this.line = i;
return this;
}
@CheckForNull
public Double getGap() {
return gap;
}
public IssueDto setGap(@Nullable Double d) {
checkArgument(d == null || d >= 0, "Value of issue gap must be positive: %d", d);
this.gap = d;
return this;
}
@CheckForNull
public Long getEffort() {
return effort;
}
public IssueDto setEffort(@Nullable Long l) {
checkArgument(l == null || l >= 0, "Value of issue effort must be positive: %d", l);
this.effort = l;
return this;
}
public String getStatus() {
return status;
}
public IssueDto setStatus(@Nullable String s) {
checkArgument(s == null || s.length() <= 20, "Value is too long for issue status: %s", s);
this.status = s;
return this;
}
@CheckForNull
public String getResolution() {
return resolution;
}
public IssueDto setResolution(@Nullable String s) {
checkArgument(s == null || s.length() <= 20, "Value is too long for issue resolution: %s", s);
this.resolution = s;
return this;
}
@CheckForNull
public String getChecksum() {
return checksum;
}
public IssueDto setChecksum(@Nullable String s) {
checkArgument(s == null || s.length() <= 1000, "Value is too long for issue checksum: %s", s);
this.checksum = s;
return this;
}
@CheckForNull
public String getAssignee() {
return assignee;
}
public IssueDto setAssignee(@Nullable String s) {
checkArgument(s == null || s.length() <= 255, "Value is too long for issue assignee: %s", s);
this.assignee = s;
return this;
}
@CheckForNull
public String getAuthorLogin() {
return authorLogin;
}
public IssueDto setAuthorLogin(@Nullable String s) {
checkArgument(s == null || s.length() <= 255, "Value is too long for issue author login: %s", s);
this.authorLogin = s;
return this;
}
@CheckForNull
public String getIssueAttributes() {
return issueAttributes;
}
public IssueDto setIssueAttributes(@Nullable String s) {
checkArgument(s == null || s.length() <= 4000, "Value is too long for issue attributes: %s", s);
this.issueAttributes = s;
return this;
}
/**
* Technical date
*/
public long getCreatedAt() {
return createdAt;
}
public IssueDto setCreatedAt(long createdAt) {
this.createdAt = createdAt;
return this;
}
/**
* Technical date
*/
public long getUpdatedAt() {
return updatedAt;
}
public IssueDto setUpdatedAt(long updatedAt) {
this.updatedAt = updatedAt;
return this;
}
public Long getIssueCreationTime() {
return issueCreationDate;
}
public IssueDto setIssueCreationTime(Long time) {
this.issueCreationDate = time;
return this;
}
public Date getIssueCreationDate() {
return longToDate(issueCreationDate);
}
public IssueDto setIssueCreationDate(@Nullable Date d) {
this.issueCreationDate = dateToLong(d);
return this;
}
public Long getIssueUpdateTime() {
return issueUpdateDate;
}
public IssueDto setIssueUpdateTime(Long time) {
this.issueUpdateDate = time;
return this;
}
public Date getIssueUpdateDate() {
return longToDate(issueUpdateDate);
}
public IssueDto setIssueUpdateDate(@Nullable Date d) {
this.issueUpdateDate = dateToLong(d);
return this;
}
public Long getIssueCloseTime() {
return issueCloseDate;
}
public IssueDto setIssueCloseTime(Long time) {
this.issueCloseDate = time;
return this;
}
public Date getIssueCloseDate() {
return longToDate(issueCloseDate);
}
public IssueDto setIssueCloseDate(@Nullable Date d) {
this.issueCloseDate = dateToLong(d);
return this;
}
public String getRule() {
return ruleKey;
}
public IssueDto setRule(RuleDefinitionDto rule) {
Preconditions.checkNotNull(rule.getId(), "Rule must be persisted.");
this.ruleId = rule.getId();
this.ruleKey = rule.getRuleKey();
this.ruleRepo = rule.getRepositoryKey();
this.language = rule.getLanguage();
return this;
}
public String getRuleRepo() {
return ruleRepo;
}
public RuleKey getRuleKey() {
return RuleKey.of(ruleRepo, ruleKey);
}
public String getLanguage() {
return language;
}
/**
* Should only be used to persist in E/S
* <p/>
* Please use {@link #setRule(RuleDefinitionDto)} instead
*/
public IssueDto setLanguage(String language) {
this.language = language;
return this;
}
public String getComponentKey() {
return componentKey;
}
/**
* Should only be used to persist in E/S
* <p/>
* Please use {@link #setComponent(ComponentDto)} instead
*/
public IssueDto setComponentKey(String componentKey) {
this.componentKey = componentKey;
return this;
}
/**
* Can be null on Views or Devs
*/
@CheckForNull
public String getComponentUuid() {
return componentUuid;
}
/**
* Should only be used to persist in E/S
* <p/>
* Please use {@link #setComponent(ComponentDto)} instead
*/
public IssueDto setComponentUuid(@Nullable String s) {
checkArgument(s == null || s.length() <= 50, "Value is too long for column ISSUES.COMPONENT_UUID: %s", s);
this.componentUuid = s;
return this;
}
@CheckForNull
public String getModuleUuid() {
return moduleUuid;
}
/**
* Should only be used to persist in E/S
* <p/>
* Please use {@link #setComponent(ComponentDto)} instead
*/
public IssueDto setModuleUuid(@Nullable String moduleUuid) {
this.moduleUuid = moduleUuid;
return this;
}
@CheckForNull
public String getModuleUuidPath() {
return moduleUuidPath;
}
/**
* Should only be used to persist in E/S
* <p/>
* Please use {@link #setComponent(ComponentDto)} instead
*/
public IssueDto setModuleUuidPath(@Nullable String moduleUuidPath) {
this.moduleUuidPath = moduleUuidPath;
return this;
}
/**
* Used by the issue tracking mechanism, but it should used the component uuid instead
*/
public String getProjectKey() {
return projectKey;
}
/**
* Should only be used to persist in E/S
* <p/>
* Please use {@link #setProject(ComponentDto)} instead
*/
public IssueDto setProjectKey(String projectKey) {
this.projectKey = projectKey;
return this;
}
/**
* Can be null on Views or Devs
*/
@CheckForNull
public String getProjectUuid() {
return projectUuid;
}
/**
* Should only be used to persist in E/S
* <p/>
* Please use {@link #setProject(ComponentDto)} instead
*/
public IssueDto setProjectUuid(@Nullable String s) {
checkArgument(s == null || s.length() <= 50, "Value is too long for column ISSUES.PROJECT_UUID: %s", s);
this.projectUuid = s;
return this;
}
@CheckForNull
public Long getSelectedAt() {
return selectedAt;
}
public IssueDto setSelectedAt(@Nullable Long d) {
this.selectedAt = d;
return this;
}
/**
* Should only be used to persist in E/S
* <p/>
* Please use {@link #setRule(RuleDefinitionDto)} instead
*/
public IssueDto setRuleKey(String repo, String rule) {
this.ruleRepo = repo;
this.ruleKey = rule;
return this;
}
/**
* Should only be used to persist in E/S
* <p/>
* Please use {@link #setProject(ComponentDto)} instead
*/
public String getFilePath() {
return filePath;
}
/**
* Should only be used to persist in E/S
* <p/>
* Please use {@link #setProject(ComponentDto)} instead
*/
public IssueDto setFilePath(String filePath) {
this.filePath = filePath;
return this;
}
public Set<String> getTags() {
return ImmutableSet.copyOf(TAGS_SPLITTER.split(tags == null ? "" : tags));
}
public IssueDto setTags(@Nullable Collection<String> tags) {
if (tags == null || tags.isEmpty()) {
setTagsString(null);
} else {
setTagsString(TAGS_JOINER.join(tags));
}
return this;
}
public IssueDto setTagsString(@Nullable String s) {
checkArgument(s == null || s.length() <= 4000, "Value is too long for column ISSUES.TAGS: %s", s);
this.tags = s;
return this;
}
public String getTagsString() {
return tags;
}
@CheckForNull
public byte[] getLocations() {
return locations;
}
@CheckForNull
public DbIssues.Locations parseLocations() {
if (locations != null) {
try {
return DbIssues.Locations.parseFrom(locations);
} catch (InvalidProtocolBufferException e) {
throw new IllegalStateException(String.format("Fail to read ISSUES.LOCATIONS [KEE=%s]", kee), e);
}
}
return null;
}
public IssueDto setLocations(@Nullable byte[] locations) {
this.locations = locations;
return this;
}
public IssueDto setLocations(@Nullable DbIssues.Locations locations) {
if (locations == null) {
this.locations = null;
} else {
this.locations = locations.toByteArray();
}
return this;
}
public int getType() {
return type;
}
public IssueDto setType(int type) {
this.type = type;
return this;
}
public IssueDto setType(RuleType type) {
this.type = type.getDbConstant();
return this;
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
public DefaultIssue toDefaultIssue() {
DefaultIssue issue = new DefaultIssue();
issue.setKey(kee);
issue.setType(RuleType.valueOf(type));
issue.setStatus(status);
issue.setResolution(resolution);
issue.setMessage(message);
issue.setGap(gap);
issue.setEffort(effort != null ? Duration.create(effort) : null);
issue.setLine(line);
issue.setChecksum(checksum);
issue.setSeverity(severity);
issue.setAssignee(assignee);
issue.setAttributes(KeyValueFormat.parse(MoreObjects.firstNonNull(issueAttributes, "")));
issue.setComponentKey(componentKey);
issue.setComponentUuid(componentUuid);
issue.setModuleUuid(moduleUuid);
issue.setModuleUuidPath(moduleUuidPath);
issue.setProjectUuid(projectUuid);
issue.setProjectKey(projectKey);
issue.setManualSeverity(manualSeverity);
issue.setRuleKey(getRuleKey());
issue.setTags(getTags());
issue.setLanguage(language);
issue.setAuthorLogin(authorLogin);
issue.setNew(false);
issue.setCreationDate(longToDate(issueCreationDate));
issue.setCloseDate(longToDate(issueCloseDate));
issue.setUpdateDate(longToDate(issueUpdateDate));
issue.setSelectedAt(selectedAt);
issue.setLocations(parseLocations());
return issue;
}
}