Compare commits

...

9 Commits

@ -109,6 +109,29 @@
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.21.6</version>
</dependency>
<dependency>
<groupId>net.jpountz.lz4</groupId>
<artifactId>lz4</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>20.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>

@ -14,7 +14,7 @@ public class ThreadPoolConfig {
@Primary
public ExecutorService sonarScannerPool() {
ThreadFactory sonarScannerPool = new CustomizableThreadFactory("sonarScanner-pool-");
return new ThreadPoolExecutor(10, 10, 0,
return new ThreadPoolExecutor(50, 50, 0,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(10000), sonarScannerPool,
new ThreadPoolExecutor.AbortPolicy());
}
@ -23,7 +23,7 @@ public class ThreadPoolConfig {
@Bean("sonarQueryResultPool")
public ExecutorService queryResultPool() {
ThreadFactory sonarQueryResultPool = new CustomizableThreadFactory("sonarQueryResult-pool-");
return new ThreadPoolExecutor(5, 5, 0,
return new ThreadPoolExecutor(50, 50, 0,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(10000),
sonarQueryResultPool,
new ThreadPoolExecutor.AbortPolicy());

@ -16,8 +16,10 @@ public class Constant {
public static final String CXX = "cpp";
public static final String CPP = "c++";
public static final String PYTHON = "python";
public static final String H = "h";
public static final String OTHER = "other";
public static final List<String> language = Arrays.asList(JAVA, C, CXX, PYTHON);
public static final List<String> language = Arrays.asList(JAVA, C, CXX, PYTHON, OTHER, H);
public static final String SUCCESS = "SUCCESS";

@ -6,14 +6,18 @@ import net.educoder.ecsonar.model.RollPage;
import net.educoder.ecsonar.model.api.QualityInspect;
import net.educoder.ecsonar.model.api.QualityInspectIsCompleted;
import net.educoder.ecsonar.model.api.QualityInspectResultData;
import net.educoder.ecsonar.model.vo.QualityInspectUserDataVO;
import net.educoder.ecsonar.model.vo.QualityInspectVO;
import net.educoder.ecsonar.model.dto.AnalyseDetailDTO;
import net.educoder.ecsonar.model.dto.AnalyseDetailListDTO;
import net.educoder.ecsonar.model.dto.CodeDetailDTO;
import net.educoder.ecsonar.model.dto.ProblemAnalysisDTO;
import net.educoder.ecsonar.model.vo.*;
import net.educoder.ecsonar.services.QualityInspectService;
import net.educoder.ecsonar.utils.ResponseResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
/**
@ -31,18 +35,19 @@ public class QualityInspectController {
/**
*
* @param language
*
* @param language
* @param homeworkId id
* @param userDatas
* @param userDatas
* @return
*/
@RequestMapping(value = "qualityInspect", method = RequestMethod.POST)
@ResponseBody
public ResponseResult<String> qualityInspect(@RequestParam String language,
@RequestParam String homeworkId,
@RequestParam String userDatas){
@RequestParam String userDatas) {
List<QualityInspectUserDataVO> userDataVOList = JSONArray.parseArray(userDatas,QualityInspectUserDataVO.class);
List<QualityInspectUserDataVO> userDataVOList = JSONArray.parseArray(userDatas, QualityInspectUserDataVO.class);
QualityInspectVO qualityInspectVO = new QualityInspectVO(language, homeworkId, userDataVOList);
if (!Constant.language.contains(qualityInspectVO.getLanguage())) {
@ -55,12 +60,13 @@ public class QualityInspectController {
/**
*
*
* @param taskId
* @return
*/
@RequestMapping(value = "qualityInspectIsCompleted", method = RequestMethod.GET)
@ResponseBody
public ResponseResult<QualityInspectIsCompleted> qualityInspectIsCompleted(@RequestParam String taskId){
public ResponseResult<QualityInspectIsCompleted> qualityInspectIsCompleted(@RequestParam String taskId) {
QualityInspectIsCompleted result = qualityInspectService.qualityInspectIsCompleted(taskId);
return ResponseResult.success(result);
}
@ -68,6 +74,7 @@ public class QualityInspectController {
/**
*
*
* @param taskId
* @return
*/
@ -76,19 +83,71 @@ public class QualityInspectController {
public ResponseResult<RollPage<QualityInspectResultData>> qualityInspectResultQuery(
@RequestParam(defaultValue = "50") Integer pageSize,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam String taskId){
@RequestParam String taskId) {
if (pageNum <= 1) {
pageNum = 1;
}
QualityInspectIsCompleted isCompleted = qualityInspectService.qualityInspectIsCompleted(taskId);
if(isCompleted.getCompleted() != 1){
if (isCompleted.getCompleted() != 1) {
return ResponseResult.error("质量检测正在处理中");
}
RollPage<QualityInspectResultData> result = qualityInspectService.qualityInspectResultQuery(pageNum, pageSize,taskId);
RollPage<QualityInspectResultData> result = qualityInspectService.qualityInspectResultQuery(pageNum, pageSize, taskId);
return ResponseResult.success(result);
}
/**
*
*
* @return
*/
@GetMapping("/analyseDetail")
@ResponseBody
public ResponseResult<AnalyseDetailDTO> analyseDetail(@Valid AnalyseDetailVO analyseDetailVO) {
AnalyseDetailDTO analyseDetailDTO = qualityInspectService.getAnalyseDetail(analyseDetailVO);
return ResponseResult.success(analyseDetailDTO);
}
/**
*
*
* @return
*/
@GetMapping("/analyseDetailList")
@ResponseBody
public ResponseResult<RollPage> analyseDetailList(@Valid AnalyseDetailListVO analyseDetailListVO) {
RollPage<AnalyseDetailListDTO> rollPage = qualityInspectService.getAnalyseDetailList(analyseDetailListVO);
return ResponseResult.success(rollPage);
}
/**
*
*
* @return
*/
@GetMapping("/problemAnalysis")
@ResponseBody
public ResponseResult<ProblemAnalysisDTO> problemAnalysis(@RequestParam Integer ruleId) {
ProblemAnalysisDTO problemAnalysis = qualityInspectService.getProblemAnalysis(ruleId);
return ResponseResult.success(problemAnalysis);
}
/**
*
*
* @return
*/
@GetMapping("/codeDetail")
@ResponseBody
public ResponseResult<CodeDetailDTO> codeDetail(@Valid CodeDetailVO codeDetailVO) {
CodeDetailDTO codeDetail = qualityInspectService.getCodeDetail(codeDetailVO);
return ResponseResult.success(codeDetail);
}
}

@ -38,7 +38,7 @@ public interface CloudTaskInfoDetailDao {
* @param taskId
* @return
*/
@Select("select count(1) from cloud_task_info_detail where task_id=#{taskId} and status=1")
@Select("select count(1) from cloud_task_info_detail where task_id=#{taskId} and status!=0")
Integer selectCountByTaskId(String taskId);

@ -0,0 +1,19 @@
package net.educoder.ecsonar.dao;
import net.educoder.ecsonar.model.FileSource;
import org.apache.ibatis.annotations.Param;
/**
* @Author: youys
* @Date: 2022/12/20
* @Description:
*/
public interface FileSourceDao {
/**
* file_uuid
* @param fileUuid
* @return
*/
FileSource findFileSourceFileUuid(@Param("fileUuid") String fileUuid);
}

@ -9,7 +9,11 @@ import org.apache.ibatis.annotations.Select;
* @Description:
*/
public interface GameCodesDao {
@Select("select new_code code,path from game_codes where id=#{id}")
GameCodes queryOriginalCodeById(Long id);
@Select("select code,path from student_work_shixun_codes where id=#{id}")
GameCodes queryShiXunOriginalCodeById(Long id);
}

@ -0,0 +1,52 @@
package net.educoder.ecsonar.dao;
import net.educoder.ecsonar.model.Issues;
import net.educoder.ecsonar.model.dto.DegreeDTO;
import java.util.List;
/**
* @Author: youys
* @Date: 2022/12/20
* @Description:
*/
public interface IssuesDao {
/**
* issue
* @param projectUuid
* @param issueType
* @param severity
* @return
*/
Integer getPageIssuesCount(String projectUuid,Integer issueType, String severity);
/**
* issue
* @param projectUuid
* @param issueType
* @param severity
* @param start
* @param pageSize
* @return
*/
List<Issues> getPageIssues(String projectUuid,Integer issueType, String severity, Integer start, Integer pageSize);
/**
*
* @param projectUuid
* @param issueType
* @return
*/
DegreeDTO queryDegree(String projectUuid, Integer issueType, Integer metricId);
/**
* id
* @param issueId
* @return
*/
Issues queryById(Long issueId);
}

@ -3,6 +3,7 @@ package net.educoder.ecsonar.dao;
import net.educoder.ecsonar.model.IssuesMetrics;
import net.educoder.ecsonar.model.Metrics;
import net.educoder.ecsonar.model.Project;
import net.educoder.ecsonar.model.Rule;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
@ -58,4 +59,11 @@ public interface ProjectDao {
"(select count(1) from issues where project_uuid=#{projectUuid} and issue_type=3) vulnerability," +
"(select value from project_measures pm where component_uuid=#{projectUuid} and metric_id=3) codeLines")
IssuesMetrics selectIssuesMetrics(String projectUuid);
/**
* rule
* @param id
* @return
*/
Rule findRuleById(@Param("id") Integer id);
}

@ -20,7 +20,8 @@ public interface TaskInfoDetailDao {
*
* @param taskInfoDetail
*/
@Insert("insert into task_info_detail(id,task_id,project_name,student_id,user_id,name,code_ids) values(#{id},#{taskId},#{projectName},#{studentId},#{userId} ,#{name},#{codeIds})")
@Insert("insert into task_info_detail(id,task_id,project_name,student_id,user_id,name,code_ids, shixun_code_ids) " +
"values(#{id},#{taskId},#{projectName},#{studentId},#{userId} ,#{name},#{codeIds}, #{shiXunCodeIds})")
void insertTaskInfoDetail(TaskInfoDetail taskInfoDetail);
/**
@ -47,7 +48,9 @@ public interface TaskInfoDetailDao {
* @param id
* @return
*/
@Select("select id,task_id taskId,project_name projectName,student_id studentId,user_id userId,name,code_ids codeIds,status,create_time createTime,update_time updateTime from task_info_detail where id=#{id}")
@Select("select id,task_id taskId,project_name projectName,student_id studentId,user_id userId,name," +
"code_ids codeIds,shixun_code_ids shiXunCodeIds, status,create_time createTime,update_time updateTime " +
"from task_info_detail where id=#{id}")
TaskInfoDetail selectById(String id);
/**
@ -69,6 +72,9 @@ public interface TaskInfoDetailDao {
* @param position
* @return
*/
@Select("select id,task_id taskId,project_name projectName,student_id studentId,user_id userId,name,code_ids codeIds,status,create_time createTime,update_time updateTime from task_info_detail where task_id=#{taskId} limit #{pageSize} offset #{position}")
List<TaskInfoDetail> selectTaskInfoDetailPageList(@Param("taskId") String taskId, @Param("pageSize") Integer pageSize, @Param("position") Integer position);
@Select("select id,task_id taskId,project_name projectName,student_id studentId,user_id userId,name," +
"code_ids codeIds,shixun_code_ids shiXunCodeIds, status,create_time createTime,update_time updateTime " +
"from task_info_detail where task_id=#{taskId} limit #{pageSize} offset #{position}")
List<TaskInfoDetail> selectTaskInfoDetailPageList(@Param("taskId") String taskId,
@Param("pageSize") Integer pageSize, @Param("position") Integer position);
}

@ -0,0 +1,42 @@
package net.educoder.ecsonar.enums;
/**
* @Author: youys
* @Date: 2022/12/20
* @Description:
*/
public enum AnalyseTypeEnum {
CodeSmell(1,80),
BUG(2, 89),
Vulnerability(3,93);
private Integer type;
private Integer metricId;
AnalyseTypeEnum(Integer type, Integer metricId) {
this.type = type;
this.metricId = metricId;
}
public Integer getType() {
return type;
}
public Integer getMetricId() {
return metricId;
}
public static AnalyseTypeEnum getAnalyseTypeEnum(Integer type) {
for (AnalyseTypeEnum analyseType : values()) {
if (analyseType.type.equals(type)) {
return analyseType;
}
}
throw new RuntimeException("Not Found AnalyseTypeEnum by type=" + type);
}
}

@ -0,0 +1,68 @@
package net.educoder.ecsonar.enums;
/**
* @Author: youys
* @Date: 2022/12/20
* @Description:
*/
public enum DegreeEnum {
All(0, "ALL", "全部"),
Blocker(1, "BLOCKER", "阻断"),
Critical(2, "CRITICAL", "严重"),
Major(3, "MAJOR", "主要"),
Minor(4, "MINOR", "次要"),
Info(5, "INFO", "提示");
/**
*
*/
private Integer type;
/**
*
*/
private String value;
/**
*
*/
private String desc;
DegreeEnum(Integer type, String value, String desc) {
this.type = type;
this.value = value;
this.desc = desc;
}
public static DegreeEnum getDegreeEnum(Integer type) {
for (DegreeEnum de : values()) {
if (de.type.equals(type)) {
return de;
}
}
throw new RuntimeException("Not Found DegreeEnum by type=" + type);
}
public static DegreeEnum getDegreeEnumByValue(String value) {
for (DegreeEnum de : values()) {
if (de.value.equals(value)) {
return de;
}
}
throw new RuntimeException("Not Found DegreeEnum by value=" + value);
}
public Integer getType() {
return type;
}
public String getValue() {
return value;
}
public String getDesc() {
return desc;
}
}

@ -0,0 +1,62 @@
package net.educoder.ecsonar.model;
import com.google.protobuf.CodedInputStream;
import com.google.protobuf.InvalidProtocolBufferException;
import lombok.Data;
import net.educoder.ecsonar.protobuf.DbFileSources;
import net.jpountz.lz4.LZ4BlockInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import static java.lang.String.format;
/**
* @Author: youys
* @Date: 2022/9/27
* @Description:
*/
@Data
public class FileSource {
private static final String SIZE_LIMIT_EXCEEDED_EXCEPTION_MESSAGE = "Protocol message was too large. May be malicious. " +
"Use CodedInputStream.setSizeLimit() to increase the size limit.";
private Long id;
private String projectUuid;
private String fileUuid;
private String lineHashes;
private String srcHash;
private byte[] binaryData = new byte[0];
private String dataHash;
public DbFileSources.Data decodeSourceData(byte[] binaryData) {
try {
return decodeRegularSourceData(binaryData);
} catch (IOException e) {
throw new IllegalStateException(
format("Fail to decompress and deserialize source data [id=%s,fileUuid=%s,projectUuid=%s]", id, fileUuid, projectUuid),
e);
}
}
private static DbFileSources.Data decodeRegularSourceData(byte[] binaryData) throws IOException {
try (LZ4BlockInputStream lz4Input = new LZ4BlockInputStream(new ByteArrayInputStream(binaryData))) {
return DbFileSources.Data.parseFrom(lz4Input);
} catch (InvalidProtocolBufferException e) {
if (SIZE_LIMIT_EXCEEDED_EXCEPTION_MESSAGE.equals(e.getMessage())) {
return decodeHugeSourceData(binaryData);
}
throw e;
}
}
private static DbFileSources.Data decodeHugeSourceData(byte[] binaryData) throws IOException {
try (LZ4BlockInputStream lz4Input = new LZ4BlockInputStream(new ByteArrayInputStream(binaryData))) {
CodedInputStream input = CodedInputStream.newInstance(lz4Input);
input.setSizeLimit(Integer.MAX_VALUE);
return DbFileSources.Data.parseFrom(input);
}
}
}

@ -0,0 +1,49 @@
package net.educoder.ecsonar.model;
import lombok.Data;
/**
* @Author: youys
* @Date: 2022/9/19
* @Description:
*/
@Data
public class Issues {
private Long id;
private String kee;
private Integer ruleId;
private String severity;
private String message;
private String status;
private String projectUuid;
private Integer issueType;
private Integer lines;
private byte[] locations = new byte[0];
/**
* bug
*/
private String name;
/**
* bug
*/
private String description;
/**
*
*/
private String path;
/**
* file_sourcesfile_uuid
* issuecomponent_uuid
*/
private String uuid;
/**
*
*/
private String language;
}

@ -0,0 +1,38 @@
package net.educoder.ecsonar.model;
import lombok.Data;
import java.util.Date;
/**
* @Author: youys
* @Date: 2022/10/17
* @Description:
*/
@Data
public class Rule {
private Long id;
private String name;
private String description;
/**
*
*/
private String language;
/**
* 20min
*/
private String defRemediationBaseEffort;
/**
* ","
*/
private String tags;
/**
*
*/
private Long createTime;
}

@ -16,6 +16,7 @@ public class TaskInfoDetail {
private Long userId;
private String name;
private String codeIds;
private String shiXunCodeIds;
/**
* 0 1 -1
*/
@ -103,4 +104,12 @@ public class TaskInfoDetail {
public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}
public String getShiXunCodeIds() {
return shiXunCodeIds;
}
public void setShiXunCodeIds(String shiXunCodeIds) {
this.shiXunCodeIds = shiXunCodeIds;
}
}

@ -0,0 +1,16 @@
package net.educoder.ecsonar.model.dto;
import lombok.Data;
/**
* @Author: youys
* @Date: 2022/12/20
* @Description:
*/
@Data
public class AnalyseDetailDTO {
private DegreeDTO bug;
private DegreeDTO vulnerability;
private DegreeDTO codeSmall;
}

@ -0,0 +1,50 @@
package net.educoder.ecsonar.model.dto;
import lombok.Data;
/**
* @Author: youys
* @Date: 2022/9/27
* @Description:
*/
@Data
public class AnalyseDetailListDTO {
/**
*
*/
private String name;
/**
*
*/
private String description;
/**
*
*/
private String filePath;
/**
*
*/
private Integer rowNumber;
/**
*
*/
private String language;
/**
*
*/
private String level;
/**
*
*/
private String uuid;
private Integer ruleId;
private Long issueId;
}

@ -0,0 +1,34 @@
package net.educoder.ecsonar.model.dto;
import lombok.Data;
import java.util.List;
/**
* @Author: youys
* @Date: 2022/10/17
* @Description:
*/
@Data
public class CodeDetailDTO {
/**
*
*/
private List<FileSourceDTO> codes;
/**
*
*/
private String example;
/**
*
*/
private String title;
/**
*
*/
private String errMessage;
}

@ -0,0 +1,36 @@
package net.educoder.ecsonar.model.dto;
import lombok.Data;
/**
* @Author: youys
* @Date: 2022/12/20
* @Description:
*/
@Data
public class DegreeDTO {
private String levelStr = "A";
private Integer total = 0;
/**
*
*/
private Integer blocker = 0;
/**
*
*/
private Integer critical = 0;
/**
*
*/
private Integer major = 0;
/**
*
*/
private Integer minor = 0;
}

@ -0,0 +1,16 @@
package net.educoder.ecsonar.model.dto;
import lombok.Data;
/**
* @Author: youys
* @Date: 2022/9/27
* @Description:
*/
@Data
public class FileSourceDTO {
private Integer rowNumber;
private String code;
}

@ -0,0 +1,38 @@
package net.educoder.ecsonar.model.dto;
import lombok.Data;
import java.util.List;
/**
* @Author: youys
* @Date: 2022/12/22
* @Description:
*/
@Data
public class ProblemAnalysisDTO {
private String title;
private String example;
/**
*
*/
private String language;
/**
* 20min
*/
private String constantIssue;
/**
*
*/
private List<String> tags;
/**
*
*/
private String createTime;
}

@ -0,0 +1,36 @@
package net.educoder.ecsonar.model.vo;
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
* @Author: youys
* @Date: 2022/12/20
* @Description:
*/
@Data
public class AnalyseDetailListVO extends PageVO {
/**
*
* 1
* 2 bug
* 3
*/
private Integer type;
/**
*
* all
*/
private Integer degree;
@NotBlank(message = "作业id不能为空")
private String homeworkId;
@NotBlank(message = "学号不能为空")
private String studentNo;
}

@ -0,0 +1,21 @@
package net.educoder.ecsonar.model.vo;
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
* @Author: youys
* @Date: 2022/12/20
* @Description:
*/
@Data
public class AnalyseDetailVO {
@NotBlank(message = "作业id不能为空")
private String homeworkId;
@NotBlank(message = "学号不能为空")
private String studentNo;
}

@ -0,0 +1,17 @@
package net.educoder.ecsonar.model.vo;
import lombok.Data;
/**
* @Author: youys
* @Date: 2022/9/27
* @Description:
*/
@Data
public class CodeDetailVO {
private String uuid;
private Integer ruleId;
private Long issueId;
}

@ -0,0 +1,16 @@
package net.educoder.ecsonar.model.vo;
import lombok.Data;
/**
* @Author: youys
* @Date: 2022/12/20
* @Description:
*/
@Data
public class PageVO {
private Integer currentPage = 1;
private Integer pageSize = 20;
}

@ -30,6 +30,8 @@ public class QualityInspectUserDataVO implements Serializable {
*/
private List<Long> codeIds;
private List<Long> studentWorkShixunCodeIds;
public String getStudentId() {
return studentId;
}
@ -61,4 +63,12 @@ public class QualityInspectUserDataVO implements Serializable {
public void setUserId(Long userId) {
this.userId = userId;
}
public List<Long> getStudentWorkShixunCodeIds() {
return studentWorkShixunCodeIds;
}
public void setStudentWorkShixunCodeIds(List<Long> studentWorkShixunCodeIds) {
this.studentWorkShixunCodeIds = studentWorkShixunCodeIds;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -45,6 +45,13 @@ public class DbOperateService {
return gameCode;
}
public GameCodes queryShiXunCodesById(Long id) {
DynamicDataSourceContextHolder.setContextKey(DynamicDataSourceConfig.READONLY);
GameCodes gameCode = gameCodesDao.queryShiXunOriginalCodeById(id);
DynamicDataSourceContextHolder.removeContextKey();
return gameCode;
}
/**
*
*

@ -1,22 +1,26 @@
package net.educoder.ecsonar.services;
import cn.hutool.core.date.DateUtil;
import com.google.protobuf.InvalidProtocolBufferException;
import net.educoder.ecsonar.dao.*;
import net.educoder.ecsonar.enums.AnalyseTypeEnum;
import net.educoder.ecsonar.enums.DegreeEnum;
import net.educoder.ecsonar.exception.BusinessException;
import net.educoder.ecsonar.dao.TaskInfoDao;
import net.educoder.ecsonar.dao.TaskInfoDetailDao;
import net.educoder.ecsonar.model.Metrics;
import net.educoder.ecsonar.model.RollPage;
import net.educoder.ecsonar.model.TaskInfo;
import net.educoder.ecsonar.model.TaskInfoDetail;
import net.educoder.ecsonar.model.*;
import net.educoder.ecsonar.model.api.Quality;
import net.educoder.ecsonar.model.api.QualityInspect;
import net.educoder.ecsonar.model.api.QualityInspectIsCompleted;
import net.educoder.ecsonar.model.api.QualityInspectResultData;
import net.educoder.ecsonar.model.vo.QualityInspectUserDataVO;
import net.educoder.ecsonar.model.vo.QualityInspectVO;
import net.educoder.ecsonar.model.dto.*;
import net.educoder.ecsonar.model.vo.*;
import net.educoder.ecsonar.protobuf.DbFileSources;
import net.educoder.ecsonar.protobuf.DbIssues;
import net.educoder.ecsonar.task.QualityInspectRunnable;
import net.educoder.ecsonar.utils.IdUtils;
import net.educoder.ecsonar.utils.SystemUtil;
import net.educoder.ecsonar.utils.html.HtmlSourceDecorator;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@ -26,6 +30,8 @@ import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutorService;
@ -54,6 +60,15 @@ public class QualityInspectService {
@Autowired
private ReportService reportService;
@Autowired
private IssuesDao issuesDao;
@Autowired
private ProjectDao projectDao;
@Autowired
private FileSourceDao fileSourceDao;
@Autowired
@Qualifier("sonarScannerPool")
@ -63,6 +78,8 @@ public class QualityInspectService {
@Qualifier("sonarQueryResultPool")
private ExecutorService sonarQueryResultPool;
private static final Logger logger = LoggerFactory.getLogger(QualityInspectService.class);
public QualityInspect qualityInspect(QualityInspectVO qualityInspectVO) {
@ -91,6 +108,7 @@ public class QualityInspectService {
taskInfoDetail.setStudentId(userData.getStudentId());
taskInfoDetail.setUserId(userData.getUserId());
taskInfoDetail.setCodeIds(userData.getCodeIds().toString());
taskInfoDetail.setShiXunCodeIds(userData.getStudentWorkShixunCodeIds().toString());
taskInfoDetailDao.insertTaskInfoDetail(taskInfoDetail);
@ -188,22 +206,203 @@ public class QualityInspectService {
private BigDecimal calcQualityScore(QualityInspectResultData resultData) {
if (resultData.getTotalRowNumber() <= 0) {
return null;
return BigDecimal.ZERO;
}
// 100 -(阻断 * 10 + 严重 * 5 + 主要* 3 + 次要 * 1/ 总行数 * 100
Quality bug = resultData.getBug();
BigDecimal bugScore = BigDecimal.valueOf(100).subtract(BigDecimal.valueOf(bug.getBlocker() * 10 + bug.getCritical() * 5 + bug.getMajor() * 3 + bug.getMinor() * 1).divide(BigDecimal.valueOf(resultData.getTotalRowNumber()),2,RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)));
BigDecimal bugScore = BigDecimal.valueOf(100).subtract(BigDecimal.valueOf(bug.getBlocker() * 10 + bug.getCritical() * 5 + bug.getMajor() * 3 + bug.getMinor() * 1).divide(BigDecimal.valueOf(resultData.getTotalRowNumber()), 2, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)));
Quality vulnerability = resultData.getVulnerability();
BigDecimal vulnerabilityScore = BigDecimal.valueOf(100).subtract(BigDecimal.valueOf(vulnerability.getBlocker() * 10 + vulnerability.getCritical() * 5 + vulnerability.getMajor() * 3 + vulnerability.getMinor() * 1).divide(BigDecimal.valueOf(resultData.getTotalRowNumber()),2, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)));
BigDecimal vulnerabilityScore = BigDecimal.valueOf(100).subtract(BigDecimal.valueOf(vulnerability.getBlocker() * 10 + vulnerability.getCritical() * 5 + vulnerability.getMajor() * 3 + vulnerability.getMinor() * 1).divide(BigDecimal.valueOf(resultData.getTotalRowNumber()), 2, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)));
Quality codeSmall = resultData.getCodeSmall();
BigDecimal codeSmallScore = BigDecimal.valueOf(100).subtract(BigDecimal.valueOf(codeSmall.getBlocker() * 10 + codeSmall.getCritical() * 5 + codeSmall.getMajor() * 3 + codeSmall.getMinor() * 1).divide(BigDecimal.valueOf(resultData.getTotalRowNumber()),2,RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)));
BigDecimal codeSmallScore = BigDecimal.valueOf(100).subtract(BigDecimal.valueOf(codeSmall.getBlocker() * 10 + codeSmall.getCritical() * 5 + codeSmall.getMajor() * 3 + codeSmall.getMinor() * 1).divide(BigDecimal.valueOf(resultData.getTotalRowNumber()), 2, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)));
// 总得分= bugScore * 0.5 + vulnerabilityScore * 0.3 + codeSmallScore * 0.2
BigDecimal score = bugScore.multiply(BigDecimal.valueOf(0.5)).add(vulnerabilityScore.multiply(BigDecimal.valueOf(0.3))).add(codeSmallScore.multiply(BigDecimal.valueOf(0.2))).setScale(2, RoundingMode.HALF_UP);
return score;
}
/**
*
*
* @param analyseDetailVO
* @return
*/
public AnalyseDetailDTO getAnalyseDetail(AnalyseDetailVO analyseDetailVO) {
// 作业id+学号
String projectName = String.format("%s-%s", analyseDetailVO.getHomeworkId(), analyseDetailVO.getStudentNo());
Project project = projectDao.findByName(projectName);
if (project == null) {
throw new BusinessException(-1, String.format("找不到分析记录homeworkId:%s,studentNo:%s", analyseDetailVO.getHomeworkId(), analyseDetailVO.getStudentNo()));
}
DegreeDTO codeSmall = issuesDao.queryDegree(project.getProject_uuid(), AnalyseTypeEnum.CodeSmell.getType(), AnalyseTypeEnum.CodeSmell.getMetricId());
DegreeDTO bug = issuesDao.queryDegree(project.getProject_uuid(), AnalyseTypeEnum.BUG.getType(), AnalyseTypeEnum.BUG.getMetricId());
DegreeDTO vulnerability = issuesDao.queryDegree(project.getProject_uuid(), AnalyseTypeEnum.Vulnerability.getType(), AnalyseTypeEnum.Vulnerability.getMetricId());
AnalyseDetailDTO analyseDetail = new AnalyseDetailDTO();
analyseDetail.setBug(bug == null ? new DegreeDTO() : bug);
analyseDetail.setVulnerability(vulnerability == null ? new DegreeDTO() : vulnerability);
analyseDetail.setCodeSmall(codeSmall == null ? new DegreeDTO() : codeSmall);
return analyseDetail;
}
/**
*
*
* @param analyseDetailListVO
* @return
*/
public RollPage<AnalyseDetailListDTO> getAnalyseDetailList(AnalyseDetailListVO analyseDetailListVO) {
RollPage rollPage = new RollPage();
if (analyseDetailListVO.getCurrentPage() <= 0) {
analyseDetailListVO.setCurrentPage(1);
}
if (analyseDetailListVO.getPageSize() >= 10000) {
analyseDetailListVO.setPageSize(10000);
}
rollPage.setCurrentPage(analyseDetailListVO.getCurrentPage());
rollPage.setPageSize(analyseDetailListVO.getPageSize());
// 作业id+学号
String projectName = String.format("%s-%s", analyseDetailListVO.getHomeworkId(), analyseDetailListVO.getStudentNo());
Project project = projectDao.findByName(projectName);
if (project == null) {
throw new BusinessException(-1, "找不到分析记录");
}
// 分析类型
AnalyseTypeEnum analyseTypeEnum = AnalyseTypeEnum.getAnalyseTypeEnum(analyseDetailListVO.getType());
// 严重程度
DegreeEnum degreeEnum = DegreeEnum.getDegreeEnum(analyseDetailListVO.getDegree());
String severity = null;
if (degreeEnum != DegreeEnum.All) {
severity = degreeEnum.getValue();
}
Integer pageIssuesCount = issuesDao.getPageIssuesCount(project.getProject_uuid(), analyseTypeEnum.getType(), severity);
rollPage.setRecordSum(pageIssuesCount);
if (pageIssuesCount > 0) {
int start = (analyseDetailListVO.getCurrentPage() - 1) * analyseDetailListVO.getPageSize();
List<Issues> pageIssues = issuesDao.getPageIssues(project.getProject_uuid(), analyseTypeEnum.getType(), severity, start, analyseDetailListVO.getPageSize());
processPageIssues(pageIssues, rollPage);
} else {
rollPage.setRecordList(new ArrayList(0));
}
return rollPage;
}
public ProblemAnalysisDTO getProblemAnalysis(Integer ruleId) {
Rule rule = projectDao.findRuleById(ruleId);
ProblemAnalysisDTO problemAnalysisDTO = new ProblemAnalysisDTO();
if (rule != null) {
String example = rule.getDescription().replaceAll("<p>", "<p style=\"color: #d50000;font-size: 17px;\">")
.replaceAll("Noncompliant Code Example", "错误代码示范")
.replaceAll("Compliant Solution", "正确代码示范")
.replaceAll("Exceptions", "异常代码")
.replaceAll("See", "链接");
problemAnalysisDTO.setTitle(rule.getName());
problemAnalysisDTO.setExample(example);
problemAnalysisDTO.setLanguage("py".equalsIgnoreCase(rule.getLanguage()) ? "python" : rule.getLanguage());
problemAnalysisDTO.setConstantIssue(rule.getDefRemediationBaseEffort());
if (StringUtils.isBlank(rule.getTags())) {
problemAnalysisDTO.setTags(new ArrayList<>());
} else {
problemAnalysisDTO.setTags(Arrays.asList(rule.getTags().split(",")));
}
problemAnalysisDTO.setCreateTime(DateUtil.formatDateTime(new Date(rule.getCreateTime())));
} else {
problemAnalysisDTO.setTitle("");
problemAnalysisDTO.setExample("");
problemAnalysisDTO.setLanguage("");
problemAnalysisDTO.setConstantIssue("");
problemAnalysisDTO.setTags(new ArrayList<>());
problemAnalysisDTO.setCreateTime(DateUtil.formatTime(new Date()));
}
return problemAnalysisDTO;
}
/**
*
*
* @param codeDetailVO
* @return
*/
public CodeDetailDTO getCodeDetail(CodeDetailVO codeDetailVO) {
FileSource fileSource = fileSourceDao.findFileSourceFileUuid(codeDetailVO.getUuid());
DbFileSources.Data data = fileSource.decodeSourceData(fileSource.getBinaryData());
List<DbFileSources.Line> linesList = data.getLinesList();
List<FileSourceDTO> fileSourceDTOList = new ArrayList<>(linesList.size());
for (DbFileSources.Line line : linesList) {
FileSourceDTO fileSourceDTO = new FileSourceDTO();
fileSourceDTO.setRowNumber(line.getLine());
fileSourceDTO.setCode(HtmlSourceDecorator.getInstance().getDecoratedSourceAsHtml(line.getSource(), line.getHighlighting(), line.getSymbols()));
fileSourceDTOList.add(fileSourceDTO);
}
CodeDetailDTO codeDetail = new CodeDetailDTO();
codeDetail.setCodes(fileSourceDTOList);
Issues issues = issuesDao.queryById(codeDetailVO.getIssueId());
ProblemAnalysisDTO problemAnalysis = getProblemAnalysis(codeDetailVO.getRuleId());
codeDetail.setExample(problemAnalysis.getExample());
codeDetail.setTitle(problemAnalysis.getTitle());
codeDetail.setErrMessage(issues.getMessage());
return codeDetail;
}
private void processPageIssues(List<Issues> pageIssues, RollPage<AnalyseDetailListDTO> rollPage) {
List<AnalyseDetailListDTO> analyseDetailLists = new ArrayList<>(pageIssues.size());
for (Issues pageIssue : pageIssues) {
AnalyseDetailListDTO detailListDTO = new AnalyseDetailListDTO();
analyseDetailLists.add(detailListDTO);
detailListDTO.setName(pageIssue.getName());
detailListDTO.setDescription(pageIssue.getMessage());
detailListDTO.setIssueId(pageIssue.getId());
detailListDTO.setUuid(pageIssue.getUuid());
detailListDTO.setRuleId(pageIssue.getRuleId());
detailListDTO.setFilePath(pageIssue.getPath());
detailListDTO.setLanguage(pageIssue.getLanguage());
detailListDTO.setLevel(DegreeEnum.getDegreeEnumByValue(pageIssue.getSeverity()).getDesc());
try {
DbIssues.Locations locations = DbIssues.Locations.parseFrom(pageIssue.getLocations());
detailListDTO.setRowNumber(locations.getTextRange().getStartLine());
} catch (InvalidProtocolBufferException e) {
detailListDTO.setRowNumber(0);
logger.error("Fail to read ISSUES.LOCATIONS [KEE=%s]", e);
}
}
rollPage.setRecordList(analyseDetailLists);
}
}

@ -8,6 +8,7 @@ import net.educoder.ecsonar.model.TaskInfoDetail;
import net.educoder.ecsonar.model.vo.QualityInspectUserDataVO;
import net.educoder.ecsonar.services.DbOperateService;
import net.educoder.ecsonar.services.SonarService;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.tomcat.util.http.fileupload.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -15,8 +16,11 @@ import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.function.Function;
/**
@ -41,9 +45,9 @@ public class QualityInspectRunnable implements Runnable {
private SonarService sonarService;
public QualityInspectRunnable(String taskId, String language, String homeworkId,
TaskInfoDetail taskInfoDetail,
QualityInspectUserDataVO userData,
ExecutorService queryResultPool) {
TaskInfoDetail taskInfoDetail,
QualityInspectUserDataVO userData,
ExecutorService queryResultPool) {
this.taskId = taskId;
this.language = language;
this.homeworkId = homeworkId;
@ -59,19 +63,16 @@ public class QualityInspectRunnable implements Runnable {
String path = String.format("/tmp/%s/%s/", homeworkId, projectName);
List<Long> codeIds = userData.getCodeIds();
for (Long codeId : codeIds) {
File file = new File(path);
try {
FileUtils.forceMkdir(file);
} catch (IOException e) {
}
List<Long> shiXunCodeIds = userData.getStudentWorkShixunCodeIds();
GameCodes gameCodes = dbOperateService.queryGameCodesById(codeId);
if(gameCodes == null){
LOGGER.error("projectName:{}, game_codes_id:{}找不到对应的学员代码",projectName, codeId);
continue;
if(CollectionUtils.isNotEmpty(shiXunCodeIds)){
for (Long codeId : shiXunCodeIds) {
processCodeId(codeId, path, projectName, dbOperateService::queryShiXunCodesById);
}
}else{
for (Long codeId : codeIds) {
processCodeId(codeId, path, projectName, dbOperateService::queryGameCodesById);
}
FileUtil.writeString(gameCodes.getCode(), path + gameCodes.getPath(), Charset.forName("UTF-8"));
}
// 写完所有文件开始用sonar进行质量分析
@ -111,4 +112,45 @@ public class QualityInspectRunnable implements Runnable {
public void setSonarService(SonarService sonarService) {
this.sonarService = sonarService;
}
private void processCodeId(Long codeId, String path, String projectName, Function<Long, GameCodes> queryMethod) {
boolean constantCFlag = true, constantCPPFlag = true, constantJavaFlag = true, constantPyFlag = true;
File file = new File(path);
try {
FileUtils.forceMkdir(file);
} catch (IOException e) {
LOGGER.error("创建目录失败: {}", path, e);
}
GameCodes gameCodes = queryMethod.apply(codeId);
if (gameCodes == null) {
LOGGER.error("projectName:{}, game_codes_id:{} 找不到对应的学员代码", projectName, codeId);
return;
}
Path filePath = Paths.get(path, gameCodes.getPath());
FileUtil.writeString(gameCodes.getCode(), filePath.toString(), Charset.forName("UTF-8"));
String filePathLower = gameCodes.getPath().toLowerCase();
if (constantCFlag && filePathLower.matches(".*\\.c$")) {
constantCFlag = false;
} else if (constantCPPFlag && filePathLower.matches(".*\\.cpp$")) {
constantCPPFlag = false;
} else if (constantJavaFlag && filePathLower.matches(".*\\.java$")) {
constantJavaFlag = false;
} else if (constantPyFlag && filePathLower.matches(".*\\.py$")) {
constantPyFlag = false;
}
// 需要自己判别语言,Java优先
if (!constantJavaFlag) {
language = Constant.JAVA;
} else if (!constantPyFlag) {
language = Constant.PYTHON;
} else if (!constantCFlag) {
language = Constant.C;
} else if (!constantCPPFlag) {
language = Constant.CXX;
}
}
}

@ -0,0 +1,73 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 net.educoder.ecsonar.utils.html;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.Deque;
class CharactersReader {
static final int END_OF_STREAM = -1;
private final BufferedReader stringBuffer;
private final Deque<String> openTags;
private int currentValue;
private int previousValue;
private int currentIndex = -1;
public CharactersReader(BufferedReader stringBuffer) {
this.stringBuffer = stringBuffer;
this.openTags = new ArrayDeque<>();
}
boolean readNextChar() throws IOException {
previousValue = currentValue;
currentValue = stringBuffer.read();
currentIndex++;
return currentValue != END_OF_STREAM;
}
int getCurrentValue() {
return currentValue;
}
int getPreviousValue() {
return previousValue;
}
int getCurrentIndex() {
return currentIndex;
}
void registerOpenTag(String textType) {
openTags.push(textType);
}
void removeLastOpenTag() {
openTags.remove();
}
Deque<String> getOpenTags() {
return openTags;
}
}

@ -0,0 +1,133 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 net.educoder.ecsonar.utils.html;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
class DecorationDataHolder {
private static final String ENTITY_SEPARATOR = ";";
private static final String FIELD_SEPARATOR = ",";
private static final String SYMBOL_PREFIX = "sym-";
private static final String HIGHLIGHTABLE = "sym";
private List<OpeningHtmlTag> openingTagsEntries;
private int openingTagsIndex;
private List<Integer> closingTagsOffsets;
private int closingTagsIndex;
DecorationDataHolder() {
openingTagsEntries = new ArrayList<>();
closingTagsOffsets = new ArrayList<>();
}
void loadSymbolReferences(String symbolsReferences) {
String[] symbols = symbolsReferences.split(ENTITY_SEPARATOR);
for (String symbol : symbols) {
String[] symbolFields = symbol.split(FIELD_SEPARATOR);
int declarationStartOffset = Integer.parseInt(symbolFields[0]);
int declarationEndOffset = Integer.parseInt(symbolFields[1]);
int symbolLength = declarationEndOffset - declarationStartOffset;
String[] symbolOccurrences = Arrays.copyOfRange(symbolFields, 2, symbolFields.length);
loadSymbolOccurrences(declarationStartOffset, symbolLength, symbolOccurrences);
}
}
void loadLineSymbolReferences(String symbolsReferences) {
String[] symbols = symbolsReferences.split(ENTITY_SEPARATOR);
for (String symbol : symbols) {
String[] symbolFields = symbol.split(FIELD_SEPARATOR);
int startOffset = Integer.parseInt(symbolFields[0]);
int endOffset = Integer.parseInt(symbolFields[1]);
int symbolLength = endOffset - startOffset;
int symbolId = Integer.parseInt(symbolFields[2]);
loadSymbolOccurrences(symbolId, symbolLength, new String[] { Integer.toString(startOffset) });
}
}
void loadSyntaxHighlightingData(String syntaxHighlightingRules) {
String[] rules = syntaxHighlightingRules.split(ENTITY_SEPARATOR);
for (String rule : rules) {
String[] ruleFields = rule.split(FIELD_SEPARATOR);
int startOffset = Integer.parseInt(ruleFields[0]);
int endOffset = Integer.parseInt(ruleFields[1]);
if (startOffset < endOffset) {
insertAndPreserveOrder(new OpeningHtmlTag(startOffset, ruleFields[2]), openingTagsEntries);
insertAndPreserveOrder(endOffset, closingTagsOffsets);
}
}
}
List<OpeningHtmlTag> getOpeningTagsEntries() {
return openingTagsEntries;
}
OpeningHtmlTag getCurrentOpeningTagEntry() {
return openingTagsIndex < openingTagsEntries.size() ? openingTagsEntries.get(openingTagsIndex) : null;
}
void nextOpeningTagEntry() {
openingTagsIndex++;
}
List<Integer> getClosingTagsOffsets() {
return closingTagsOffsets;
}
int getCurrentClosingTagOffset() {
return closingTagsIndex < closingTagsOffsets.size() ? closingTagsOffsets.get(closingTagsIndex) : -1;
}
void nextClosingTagOffset() {
closingTagsIndex++;
}
private void loadSymbolOccurrences(int declarationStartOffset, int symbolLength, String[] symbolOccurrences) {
for (String symbolOccurrence : symbolOccurrences) {
int occurrenceStartOffset = Integer.parseInt(symbolOccurrence);
int occurrenceEndOffset = occurrenceStartOffset + symbolLength;
insertAndPreserveOrder(new OpeningHtmlTag(occurrenceStartOffset, SYMBOL_PREFIX + declarationStartOffset + " " + HIGHLIGHTABLE), openingTagsEntries);
insertAndPreserveOrder(occurrenceEndOffset, closingTagsOffsets);
}
}
private void insertAndPreserveOrder(OpeningHtmlTag newEntry, List<OpeningHtmlTag> openingHtmlTags) {
int insertionIndex = 0;
Iterator<OpeningHtmlTag> tagIterator = openingHtmlTags.iterator();
while (tagIterator.hasNext() && tagIterator.next().getStartOffset() <= newEntry.getStartOffset()) {
insertionIndex++;
}
openingHtmlTags.add(insertionIndex, newEntry);
}
private void insertAndPreserveOrder(int newOffset, List<Integer> orderedOffsets) {
int insertionIndex = 0;
Iterator<Integer> entriesIterator = orderedOffsets.iterator();
while (entriesIterator.hasNext() && entriesIterator.next() <= newOffset) {
insertionIndex++;
}
orderedOffsets.add(insertionIndex, newOffset);
}
}

@ -0,0 +1,74 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 net.educoder.ecsonar.utils.html;
import org.apache.commons.lang3.StringUtils;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
*
*/
public final class HtmlSourceDecorator {
private static volatile HtmlSourceDecorator htmlSourceDecorator = null;
private HtmlSourceDecorator() {
}
public static HtmlSourceDecorator getInstance() {
if (htmlSourceDecorator == null) {
synchronized (HtmlSourceDecorator.class) {
if (htmlSourceDecorator == null) {
htmlSourceDecorator = new HtmlSourceDecorator();
}
}
}
return htmlSourceDecorator;
}
public String getDecoratedSourceAsHtml(@NotNull String sourceLine, @NotNull String highlighting, @NotNull String symbols) {
if (sourceLine == null) {
return null;
}
DecorationDataHolder decorationDataHolder = new DecorationDataHolder();
if (StringUtils.isNotBlank(highlighting)) {
decorationDataHolder.loadSyntaxHighlightingData(highlighting);
}
if (StringUtils.isNotBlank(symbols)) {
decorationDataHolder.loadLineSymbolReferences(symbols);
}
HtmlTextDecorator textDecorator = new HtmlTextDecorator();
List<String> decoratedSource = textDecorator.decorateTextWithHtml(sourceLine, decorationDataHolder, 1, 1);
if (decoratedSource == null) {
return null;
} else {
if (decoratedSource.isEmpty()) {
return "";
} else {
return decoratedSource.get(0);
}
}
}
}

@ -0,0 +1,223 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 net.educoder.ecsonar.utils.html;
import com.google.common.io.Closeables;
import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.Nullable;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.Collection;
import java.util.List;
import static com.google.common.collect.Lists.newArrayList;
@Slf4j
public class HtmlTextDecorator {
static final char CR_END_OF_LINE = '\r';
static final char LF_END_OF_LINE = '\n';
static final char HTML_OPENING = '<';
static final char HTML_CLOSING = '>';
static final char AMPERSAND = '&';
static final String ENCODED_HTML_OPENING = "&lt;";
static final String ENCODED_HTML_CLOSING = "&gt;";
static final String ENCODED_AMPERSAND = "&amp;";
List<String> decorateTextWithHtml(String text, DecorationDataHolder decorationDataHolder) {
return decorateTextWithHtml(text, decorationDataHolder, null, null);
}
List<String> decorateTextWithHtml(String text, DecorationDataHolder decorationDataHolder, @Nullable Integer from, @Nullable Integer to) {
StringBuilder currentHtmlLine = new StringBuilder();
List<String> decoratedHtmlLines = newArrayList();
int currentLine = 1;
BufferedReader stringBuffer = null;
try {
stringBuffer = new BufferedReader(new StringReader(text));
CharactersReader charsReader = new CharactersReader(stringBuffer);
while (charsReader.readNextChar()) {
if (shouldStop(currentLine, to)) {
break;
}
if (shouldStartNewLine(charsReader)) {
if (canAddLine(currentLine, from)) {
decoratedHtmlLines.add(currentHtmlLine.toString());
}
currentLine++;
currentHtmlLine = new StringBuilder();
}
addCharToCurrentLine(charsReader, currentHtmlLine, decorationDataHolder);
}
closeCurrentSyntaxTags(charsReader, currentHtmlLine);
if (shouldStartNewLine(charsReader)) {
addLine(decoratedHtmlLines, currentHtmlLine.toString(), currentLine, from, to);
currentLine++;
addLine(decoratedHtmlLines, "", currentLine, from, to);
} else if (currentHtmlLine.length() > 0) {
addLine(decoratedHtmlLines, currentHtmlLine.toString(), currentLine, from, to);
}
} catch (IOException exception) {
String errorMsg = "An exception occurred while highlighting the syntax of one of the project's files";
log.error(errorMsg);
throw new IllegalStateException(errorMsg, exception);
} finally {
Closeables.closeQuietly(stringBuffer);
}
return decoratedHtmlLines;
}
private void addCharToCurrentLine(CharactersReader charsReader, StringBuilder currentHtmlLine, DecorationDataHolder decorationDataHolder) {
if (shouldStartNewLine(charsReader)) {
if (shouldReopenPendingTags(charsReader)) {
reopenCurrentSyntaxTags(charsReader, currentHtmlLine);
}
}
int numberOfTagsToClose = getNumberOfTagsToClose(charsReader.getCurrentIndex(), decorationDataHolder);
closeCompletedTags(charsReader, numberOfTagsToClose, currentHtmlLine);
if (shouldClosePendingTags(charsReader)) {
closeCurrentSyntaxTags(charsReader, currentHtmlLine);
}
Collection<String> tagsToOpen = getTagsToOpen(charsReader.getCurrentIndex(), decorationDataHolder);
openNewTags(charsReader, tagsToOpen, currentHtmlLine);
if (shouldAppendCharToHtmlOutput(charsReader)) {
char currentChar = (char) charsReader.getCurrentValue();
currentHtmlLine.append(normalize(currentChar));
}
}
private static void addLine(List<String> decoratedHtmlLines, String line, int currentLine, @Nullable Integer from, @Nullable Integer to) {
if (canAddLine(currentLine, from) && !shouldStop(currentLine, to)) {
decoratedHtmlLines.add(line);
}
}
private static boolean canAddLine(int currentLine, @Nullable Integer from) {
return from == null || currentLine >= from;
}
private static boolean shouldStop(int currentLine, @Nullable Integer to) {
return to != null && to < currentLine;
}
private char[] normalize(char currentChar) {
char[] normalizedChars;
if (currentChar == HTML_OPENING) {
normalizedChars = ENCODED_HTML_OPENING.toCharArray();
} else if (currentChar == HTML_CLOSING) {
normalizedChars = ENCODED_HTML_CLOSING.toCharArray();
} else if (currentChar == AMPERSAND) {
normalizedChars = ENCODED_AMPERSAND.toCharArray();
} else {
normalizedChars = new char[] {currentChar};
}
return normalizedChars;
}
private boolean shouldAppendCharToHtmlOutput(CharactersReader charsReader) {
return charsReader.getCurrentValue() != CR_END_OF_LINE && charsReader.getCurrentValue() != LF_END_OF_LINE;
}
private int getNumberOfTagsToClose(int currentIndex, DecorationDataHolder dataHolder) {
int numberOfTagsToClose = 0;
while (currentIndex == dataHolder.getCurrentClosingTagOffset()) {
numberOfTagsToClose++;
dataHolder.nextClosingTagOffset();
}
return numberOfTagsToClose;
}
private Collection<String> getTagsToOpen(int currentIndex, DecorationDataHolder dataHolder) {
Collection<String> tagsToOpen = newArrayList();
while (dataHolder.getCurrentOpeningTagEntry() != null && currentIndex == dataHolder.getCurrentOpeningTagEntry().getStartOffset()) {
tagsToOpen.add(dataHolder.getCurrentOpeningTagEntry().getCssClass());
dataHolder.nextOpeningTagEntry();
}
return tagsToOpen;
}
private boolean shouldClosePendingTags(CharactersReader charactersReader) {
return charactersReader.getCurrentValue() == CR_END_OF_LINE
|| (charactersReader.getCurrentValue() == LF_END_OF_LINE && charactersReader.getPreviousValue() != CR_END_OF_LINE)
|| (charactersReader.getCurrentValue() == CharactersReader.END_OF_STREAM && charactersReader.getPreviousValue() != LF_END_OF_LINE);
}
private boolean shouldReopenPendingTags(CharactersReader charactersReader) {
return (charactersReader.getPreviousValue() == LF_END_OF_LINE && charactersReader.getCurrentValue() != LF_END_OF_LINE)
|| (charactersReader.getPreviousValue() == CR_END_OF_LINE && charactersReader.getCurrentValue() != CR_END_OF_LINE
&& charactersReader.getCurrentValue() != LF_END_OF_LINE);
}
private boolean shouldStartNewLine(CharactersReader charactersReader) {
return charactersReader.getPreviousValue() == LF_END_OF_LINE
|| (charactersReader.getPreviousValue() == CR_END_OF_LINE && charactersReader.getCurrentValue() != LF_END_OF_LINE);
}
private void closeCompletedTags(CharactersReader charactersReader, int numberOfTagsToClose,
StringBuilder decoratedText) {
for (int i = 0; i < numberOfTagsToClose; i++) {
injectClosingHtml(decoratedText);
charactersReader.removeLastOpenTag();
}
}
private void openNewTags(CharactersReader charactersReader, Collection<String> tagsToOpen,
StringBuilder decoratedText) {
for (String tagToOpen : tagsToOpen) {
injectOpeningHtmlForRule(tagToOpen, decoratedText);
charactersReader.registerOpenTag(tagToOpen);
}
}
private void closeCurrentSyntaxTags(CharactersReader charactersReader, StringBuilder decoratedText) {
for (int i = 0; i < charactersReader.getOpenTags().size(); i++) {
injectClosingHtml(decoratedText);
}
}
private void reopenCurrentSyntaxTags(CharactersReader charactersReader, StringBuilder decoratedText) {
for (String tags : charactersReader.getOpenTags()) {
injectOpeningHtmlForRule(tags, decoratedText);
}
}
private void injectOpeningHtmlForRule(String textType, StringBuilder decoratedText) {
decoratedText.append("<span class=\"").append(textType).append("\">");
}
private void injectClosingHtml(StringBuilder decoratedText) {
decoratedText.append("</span>");
}
}

@ -0,0 +1,64 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 net.educoder.ecsonar.utils.html;
class OpeningHtmlTag {
private final int startOffset;
private final String cssClass;
OpeningHtmlTag(int startOffset, String cssClass) {
this.startOffset = startOffset;
this.cssClass = cssClass;
}
int getStartOffset() {
return startOffset;
}
String getCssClass() {
return cssClass;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
return compareTo((OpeningHtmlTag) o);
}
@Override
public int hashCode() {
int result = startOffset;
result = 31 * result + (cssClass != null ? cssClass.hashCode() : 0);
return result;
}
private boolean compareTo(OpeningHtmlTag otherTag) {
if (startOffset != otherTag.startOffset) {
return false;
}
return (cssClass != null) ? cssClass.equals(otherTag.cssClass) : (otherTag.cssClass == null);
}
}

@ -5,16 +5,16 @@
#spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.initSize=20
spring.datasource.master.url=jdbc:postgresql://127.0.0.1:5432/sonar7.7
#spring.datasource.url=jdbc:postgresql://117.50.14.123:5432/sonar
spring.datasource.master.username=root
spring.datasource.master.password=root
#spring.datasource.master.url=jdbc:postgresql://127.0.0.1:5432/sonar7.7
spring.datasource.master.url=jdbc:postgresql://106.75.25.158:5432/sonar
spring.datasource.master.username=sonar
spring.datasource.master.password=sonar
spring.datasource.master.driverClassName=org.postgresql.Driver
#### test ######
spring.datasource.readonly.driverClassName=com.mysql.jdbc.Driver
spring.datasource.readonly.url=jdbc:mysql://rm-bp13v5020p7828r5rso.mysql.rds.aliyuncs.com:3306/preeducoderweb?userSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false
spring.datasource.readonly.url=jdbc:mysql://testeducoder-public.mysql.polardb.rds.aliyuncs.com:3306/newtesteducoderweb?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false
spring.datasource.readonly.username=testeducoder
spring.datasource.readonly.password=TEST@123
@ -57,5 +57,6 @@ skip.checked=true
server.port=8081
mybatis.mapper-locations=classpath:mapper/*
sonar.host=http://localhost:9000
sonar.host=http://localhost:9000

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="net.educoder.ecsonar.dao.FileSourceDao">
<select id="findFileSourceFileUuid" parameterType="java.lang.String" resultType="net.educoder.ecsonar.model.FileSource">
select
id,
project_uuid as projectUuid,
file_uuid as fileUuid,
binary_data as binaryData,
line_hashes as lineHashes,
data_hash as dataHash,
src_hash as srcHash
from file_sources where file_uuid=#{fileUuid,jdbcType=VARCHAR}
</select>
</mapper>

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="net.educoder.ecsonar.dao.IssuesDao">
<select id="getPageIssuesCount" resultType="java.lang.Integer">
select
count(1)
from issues i
inner join rules r on i.rule_id =r.id
inner join projects p on p.uuid =i.component_uuid
where i.project_uuid = #{projectUuid,jdbcType=VARCHAR}
and i.status='OPEN'
and p.root_uuid =#{projectUuid,jdbcType=VARCHAR} and p.uuid !=#{projectUuid,jdbcType=VARCHAR} and p."scope" ='FIL'
<if test="issueType != null">
and i.issue_type=#{issueType,jdbcType=INTEGER}
</if>
<if test="severity != null">
and i.severity=#{severity,jdbcType=VARCHAR}
</if>
</select>
<select id="getPageIssues" resultType="net.educoder.ecsonar.model.Issues">
select
i.id,i.kee,i.rule_id ruleId,i.severity,i.status,i.project_uuid projectUuid,i.issue_type issueType,i.locations,i.line lines,r.name,r.description,r.language,p.path,p.uuid,i.message
from issues i
inner join rules r on i.rule_id =r.id
inner join projects p on p.uuid =i.component_uuid
where i.project_uuid = #{projectUuid,jdbcType=VARCHAR}
and i.status='OPEN'
and p.root_uuid =#{projectUuid,jdbcType=VARCHAR} and p.uuid !=#{projectUuid,jdbcType=VARCHAR} and p."scope" ='FIL'
<if test="issueType != null">
and i.issue_type=#{issueType,jdbcType=INTEGER}
</if>
<if test="severity != null">
and i.severity=#{severity,jdbcType=VARCHAR}
</if>
limit #{pageSize} offset #{start}
</select>
<select id="queryDegree" resultType="net.educoder.ecsonar.model.dto.DegreeDTO">
select
(select count(1) from issues i where project_uuid =#{projectUuid} and issue_type=#{issueType} and status='OPEN' and severity != 'INFO') total,
(select count(1) from issues i where project_uuid =#{projectUuid} and issue_type=#{issueType} and severity = 'MAJOR' and status='OPEN') major,
(select count(1) from issues i where project_uuid =#{projectUuid} and issue_type=#{issueType} and severity = 'MINOR' and status='OPEN') minor,
(select count(1) from issues i where project_uuid =#{projectUuid} and issue_type=#{issueType} and severity = 'BLOCKER' and status='OPEN') blocker,
(select count(1) from issues i where project_uuid =#{projectUuid} and issue_type=#{issueType} and severity = 'CRITICAL' and status='OPEN') critical,
(select text_value from project_measures i where component_uuid =#{projectUuid} and metric_id = #{metricId} order by id desc limit 1) levelStr
</select>
<select id="queryById" parameterType="java.lang.Long" resultType="net.educoder.ecsonar.model.Issues">
select i.id,i.kee,i.rule_id ruleId,i.severity,i.status,i.project_uuid projectUuid,i.issue_type issueType,i.locations,i.line lines,i.message from issues i where id=#{issueId}
</select>
</mapper>

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="net.educoder.ecsonar.dao.ProjectDao">
<select id="findRuleById" parameterType="java.lang.Integer" resultType="net.educoder.ecsonar.model.Rule">
select
id,
name,
description,
language,
def_remediation_base_effort defRemediationBaseEffort,
system_tags tags,
created_at createTime
from rules where id=#{id,jdbcType=INTEGER}
</select>
</mapper>

@ -0,0 +1,41 @@
// SonarQube, open source software quality management tool.
// Copyright (C) 2008-2016 SonarSource
// mailto:contact AT sonarsource DOT com
//
// SonarQube 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.
//
// SonarQube 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.
syntax = "proto2";
package sonarqube.db.commons;
// The java package can be changed without breaking compatibility.
// it impacts only the generated Java code.
option java_package = "net.educoder.quality.protobuf";
option optimize_for = SPEED;
// Lines start at 1 and line offsets start at 0
message TextRange {
// Start line. Should never be absent
optional int32 start_line = 1;
// End line (inclusive). Absent means it is same as start line
optional int32 end_line = 2;
// If absent it means range starts at the first offset of start line
optional int32 start_offset = 3;
// If absent it means range ends at the last offset of end line
optional int32 end_offset = 4;
}

@ -0,0 +1,76 @@
/*
SonarQube, open source software quality management tool.
Copyright (C) 2008-2016 SonarSource
mailto:contact AT sonarsource DOT com
SonarQube 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.
SonarQube 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.
*/
// Structure of db column FILE_SOURCES.BINARY_DATA
syntax = "proto2";
// Package must not be changed for backward-compatibility
// with the DB rows inserted in DB before 5.2
package org.sonar.server.source.db;
// The java package can be changed without breaking compatibility.
// it impacts only the generated Java code.
option java_package = "net.educoder.quality.protobuf";
option optimize_for = SPEED;
message Line {
optional int32 line = 1;
optional string source = 2;
// SCM
optional string scm_revision = 3;
optional string scm_author = 4;
optional int64 scm_date = 5;
// Deprecated fields in 6.2 (has been deprecated when merging coverage into a single metric)
// They are still used to read coverage info from sources that have not be re-analyzed
optional int32 deprecated_ut_line_hits = 6;
optional int32 deprecated_ut_conditions = 7;
optional int32 deprecated_ut_covered_conditions = 8;
optional int32 deprecated_it_line_hits = 9;
optional int32 deprecated_it_conditions = 10;
optional int32 deprecated_it_covered_conditions = 11;
optional int32 deprecated_overall_line_hits = 12;
optional int32 deprecated_overall_conditions = 13;
optional int32 deprecated_overall_covered_conditions = 14;
optional string highlighting = 15;
optional string symbols = 16;
repeated int32 duplication = 17 [packed = true];
// coverage info (since 6.2)
optional int32 line_hits = 18;
optional int32 conditions = 19;
optional int32 covered_conditions = 20;
optional bool is_new_line = 21;
}
message Range {
optional int32 startOffset = 1;
optional int32 endOffset = 2;
}
// TODO should be dropped as it prevents streaming
message Data {
repeated Line lines = 1;
}

@ -0,0 +1,47 @@
// SonarQube, open source software quality management tool.
// Copyright (C) 2008-2016 SonarSource
// mailto:contact AT sonarsource DOT com
//
// SonarQube 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.
//
// SonarQube 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.
// Structure of table ISSUES
syntax = "proto2";
package sonarqube.db.issues;
import "db-commons.proto";
// The java package can be changed without breaking compatibility.
// it impacts only the generated Java code.
option java_package = "net.educoder.quality.protobuf";
option optimize_for = SPEED;
message Locations {
optional sonarqube.db.commons.TextRange text_range = 1;
repeated Flow flow = 2;
}
message Flow {
repeated Location location = 1;
}
message Location {
optional string component_id = 1;
// Only when component is a file. Can be empty for a file if this is an issue global to the file.
optional sonarqube.db.commons.TextRange text_range = 2;
optional string msg = 3;
}

@ -0,0 +1,40 @@
/*
SonarQube, open source software quality management tool.
Copyright (C) 2008-2016 SonarSource
mailto:contact AT sonarsource DOT com
SonarQube 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.
SonarQube 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.
*/
// Structure of db column PROJECT_BRANCHES.PULL_REQUEST_DATA
syntax = "proto3";
package sonarqube.db.project_branches;
// The java package can be changed without breaking compatibility.
// it impacts only the generated Java code.
option java_package = "net.educoder.quality.protobuf";
option optimize_for = SPEED;
message PullRequestData {
string branch = 1;
string title = 2;
string url = 3;
map<string, string> attributes = 4;
string target = 5;
}
Loading…
Cancel
Save