/* * SonarQube * Copyright (C) 2009-2025 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.ce; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; import java.util.List; import javax.annotation.CheckForNull; import javax.annotation.Nullable; import org.sonar.db.DbSession; import static com.google.common.base.Preconditions.checkArgument; import static java.lang.String.format; public class CeActivityDto { private static final int ERROR_MESSAGE_MAX_SIZE = 1000; private static final int NODE_NAME_MAX_SIZE = 100; public enum Status { SUCCESS, FAILED, CANCELED } private String uuid; /** * Can be {@code null} when task is not associated to any data in table COMPONENTS, but must always be non {@code null} * at the same time as {@link #entityUuid}. *

* The component uuid of any component (project or not) is its own UUID. */ private String componentUuid; /** * Can be {@code null} when task is not associated to any component, but must always be non {@code null} * at the same time as {@link #componentUuid}. *

*/ private String entityUuid; private String analysisUuid; private Status status; private String taskType; private boolean isLast; private String isLastKey; private boolean mainIsLast; private String mainIsLastKey; private String submitterUuid; private String workerUuid; private long submittedAt; private Long startedAt; private Long executedAt; private long createdAt; private long updatedAt; private Long executionTimeMs; /** * The error message of the activity. Shall be non null only when status is FAILED. When status is FAILED, can be null * (eg. for activity created before the column has been introduced). *

* This property is populated when inserting AND when reading *

*/ private String errorMessage; /** * The error stacktrace (if any). Shall be non null only when status is FAILED. When status is FAILED, can be null * because exception such as MessageException do not have a stacktrace (ie. functional exceptions). *

* This property can be populated when inserting but is populated only when reading by a specific UUID. *

* * @see CeActivityDao#selectByUuid(DbSession, String) */ private String errorStacktrace; /** * Optional free-text type of error. It may be set only when {@link #errorMessage} is not null. */ @Nullable private String errorType; /** * Flag indicating whether the analysis of the current activity has a scanner context or not. *

* This property can not be populated when inserting but is populated when reading. */ private boolean hasScannerContext; /** * Messages attached to the current activity. *

* This property can not be populated when inserting but is populated when retrieving the activity. */ private List ceTaskMessageDtos; private String nodeName; CeActivityDto() { // required for MyBatis } public CeActivityDto(CeQueueDto queueDto) { this.uuid = queueDto.getUuid(); this.taskType = queueDto.getTaskType(); this.componentUuid = queueDto.getComponentUuid(); this.entityUuid = queueDto.getEntityUuid(); this.isLastKey = format("%s%s", taskType, Strings.nullToEmpty(componentUuid)); this.mainIsLastKey = format("%s%s", taskType, Strings.nullToEmpty(entityUuid)); this.submitterUuid = queueDto.getSubmitterUuid(); this.workerUuid = queueDto.getWorkerUuid(); this.submittedAt = queueDto.getCreatedAt(); this.startedAt = queueDto.getStartedAt(); } public String getUuid() { return uuid; } public CeActivityDto setUuid(@Nullable String s) { validateUuid(s, "UUID"); this.uuid = s; return this; } public String getTaskType() { return taskType; } public CeActivityDto setTaskType(String s) { this.taskType = s; return this; } @CheckForNull public String getComponentUuid() { return componentUuid; } public CeActivityDto setComponentUuid(@Nullable String s) { validateUuid(s, "COMPONENT_UUID"); this.componentUuid = s; return this; } @CheckForNull public String getEntityUuid() { return entityUuid; } public CeActivityDto setEntityUuid(@Nullable String s) { validateUuid(s, "ENTITY_UUID"); this.entityUuid = s; return this; } private static void validateUuid(@Nullable String s, String columnName) { checkArgument(s == null || s.length() <= 40, "Value is too long for column CE_ACTIVITY.%s: %s", columnName, s); } public Status getStatus() { return status; } public CeActivityDto setStatus(Status s) { this.status = s; return this; } public boolean getIsLast() { return isLast; } CeActivityDto setIsLast(boolean b) { this.isLast = b; this.mainIsLast = b; return this; } public String getIsLastKey() { return isLastKey; } public boolean getMainIsLast() { return mainIsLast; } public String getMainIsLastKey() { return mainIsLastKey; } @CheckForNull public String getSubmitterUuid() { return submitterUuid; } public long getSubmittedAt() { return submittedAt; } public CeActivityDto setSubmittedAt(long submittedAt) { this.submittedAt = submittedAt; return this; } @CheckForNull public Long getStartedAt() { return startedAt; } public CeActivityDto setStartedAt(@Nullable Long l) { this.startedAt = l; return this; } @CheckForNull public Long getExecutedAt() { return executedAt; } public CeActivityDto setExecutedAt(@Nullable Long l) { this.executedAt = l; return this; } public long getCreatedAt() { return createdAt; } public CeActivityDto setCreatedAt(long l) { this.createdAt = l; return this; } public long getUpdatedAt() { return updatedAt; } public CeActivityDto setUpdatedAt(long l) { this.updatedAt = l; return this; } @CheckForNull public Long getExecutionTimeMs() { return executionTimeMs; } public CeActivityDto setExecutionTimeMs(@Nullable Long l) { checkArgument(l == null || l >= 0, "Execution time must be positive: %s", l); this.executionTimeMs = l; return this; } @CheckForNull public String getAnalysisUuid() { return analysisUuid; } public CeActivityDto setAnalysisUuid(@Nullable String s) { this.analysisUuid = s; return this; } @CheckForNull public String getWorkerUuid() { return workerUuid; } public CeActivityDto setWorkerUuid(String workerUuid) { this.workerUuid = workerUuid; return this; } @CheckForNull public String getErrorMessage() { return errorMessage; } public CeActivityDto setErrorMessage(@Nullable String errorMessage) { this.errorMessage = ensureNotTooBig(removeCharZeros(errorMessage), ERROR_MESSAGE_MAX_SIZE); return this; } @CheckForNull public String getErrorType() { return errorType; } public CeActivityDto setErrorType(@Nullable String s) { this.errorType = ensureNotTooBig(s, 20); return this; } @CheckForNull public String getErrorStacktrace() { return errorStacktrace; } @CheckForNull public CeActivityDto setErrorStacktrace(@Nullable String errorStacktrace) { this.errorStacktrace = removeCharZeros(errorStacktrace); return this; } public boolean isHasScannerContext() { return hasScannerContext; } protected CeActivityDto setHasScannerContext(boolean hasScannerContext) { this.hasScannerContext = hasScannerContext; return this; } public List getCeTaskMessageDtos() { return ceTaskMessageDtos; } @VisibleForTesting protected CeActivityDto setCeTaskMessageDtos(List ceTaskMessageDtos) { this.ceTaskMessageDtos = ceTaskMessageDtos; return this; } @CheckForNull public String getNodeName() { return nodeName; } public CeActivityDto setNodeName(@Nullable String nodeName) { this.nodeName = ensureNotTooBig(nodeName, NODE_NAME_MAX_SIZE); return this; } @Override public String toString() { return "CeActivityDto{" + "uuid='" + uuid + '\'' + ", nodeName='" + nodeName + '\'' + ", componentUuid='" + componentUuid + '\'' + ", entityUuid='" + entityUuid + '\'' + ", analysisUuid='" + analysisUuid + '\'' + ", status=" + status + ", taskType='" + taskType + '\'' + ", isLast=" + isLast + ", isLastKey='" + isLastKey + '\'' + ", mainIsLast=" + mainIsLast + ", mainIsLastKey='" + mainIsLastKey + '\'' + ", submitterUuid='" + submitterUuid + '\'' + ", workerUuid='" + workerUuid + '\'' + ", submittedAt=" + submittedAt + ", startedAt=" + startedAt + ", executedAt=" + executedAt + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + ", executionTimeMs=" + executionTimeMs + ", errorMessage='" + errorMessage + '\'' + ", errorStacktrace='" + errorStacktrace + '\'' + ", hasScannerContext=" + hasScannerContext + '}'; } @CheckForNull private static String ensureNotTooBig(@Nullable String str, int maxSize) { if (str == null) { return null; } if (str.length() <= maxSize) { return str; } return str.substring(0, maxSize); } @CheckForNull private static String removeCharZeros(@Nullable String str) { if (str == null || str.isEmpty()) { return str; } return str.codePoints() .filter(c -> c != "\u0000".codePointAt(0)) .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) .toString(); } }