Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Development: Improve XML parsing #8462

Merged
merged 62 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
ccd2c57
use xml mapper for test result parsing
Apr 22, 2024
ee2bf05
remove bean
Apr 22, 2024
0f0c734
simplify
Apr 22, 2024
3cbb675
rewrite sca xml parsing with XmlMapper and DTOs
Apr 23, 2024
8f4be4c
move class into different package
Apr 23, 2024
edfdd83
move method into service
Strohgelaender Apr 23, 2024
754af7c
Merge remote-tracking branch 'origin/chore/xmlmapper' into chore/xmlm…
Strohgelaender Apr 23, 2024
62b99ca
revert unintended change
Strohgelaender Apr 23, 2024
afb1954
fix sca parsing
Strohgelaender Apr 23, 2024
1b3c7f2
use detail message to capture child xml
Apr 23, 2024
2f1281d
simplify
Strohgelaender Apr 23, 2024
a699b8c
move test result xml parser
Apr 23, 2024
f27151a
small improvements
Apr 23, 2024
854a33c
add unit test for xml parsing
Apr 23, 2024
6e7cd75
implement ugly workaround
Strohgelaender Apr 23, 2024
a62d729
fix xml format in test
Apr 23, 2024
7617f65
fix typo
Apr 23, 2024
9ca795a
extend tests
Strohgelaender Apr 23, 2024
c06a7cc
Merge remote-tracking branch 'origin/chore/xmlmapper' into chore/xmlm…
Strohgelaender Apr 23, 2024
ac89758
make parser class package private
Strohgelaender Apr 23, 2024
3857ea6
add javadoc
Strohgelaender Apr 23, 2024
b74c46b
fix sca test cases
Strohgelaender Apr 23, 2024
a4f2a0a
fix sca test cases, improve code
Strohgelaender Apr 23, 2024
b1bf023
reimplement test
Strohgelaender Apr 23, 2024
98ec446
Merge branch 'develop' into chore/xmlmapper
Strohgelaender Apr 23, 2024
661a9a5
move into method
Strohgelaender Apr 24, 2024
1cf1f51
Update src/main/java/de/tum/in/www1/artemis/service/connectors/localc…
Strohgelaender Apr 24, 2024
d6cd8f4
review
Strohgelaender Apr 24, 2024
6c47cbf
Review: Move and improve method
Strohgelaender Apr 25, 2024
22f140c
Merge branch 'develop' into chore/xmlmapper
Apr 26, 2024
2dfc5e2
fix error after merge
Strohgelaender Apr 26, 2024
14e8a90
Merge branch 'develop' into chore/xmlmapper
Strohgelaender Apr 27, 2024
e078ddd
Merge branch 'develop' into chore/xmlmapper
Strohgelaender Apr 27, 2024
67b6f61
Merge branch 'develop' into chore/xmlmapper
Apr 28, 2024
4619a69
Merge branch 'develop' into chore/xmlmapper
Apr 28, 2024
df93ec2
try to handle null commit hash values
Apr 28, 2024
c0a923a
delete unused code
May 1, 2024
6ccc949
Merge branch 'develop' into chore/xmlmapper
May 1, 2024
6ba30f2
Merge branch 'develop' into chore/xmlmapper
krusche May 2, 2024
515f9a5
merge
Strohgelaender May 5, 2024
78d6a1c
Merge branch 'develop' into chore/xmlmapper
Strohgelaender May 9, 2024
2a9768b
Merge branch 'develop' of https://github.com/ls1intum/Artemis into ch…
Strohgelaender May 11, 2024
85f7637
merge
Strohgelaender May 15, 2024
9f8cf86
merge
Strohgelaender May 17, 2024
8458095
Merge branch 'develop' of https://github.com/ls1intum/Artemis into ch…
Strohgelaender May 18, 2024
294e3ae
Merge branch 'develop' into chore/xmlmapper
May 18, 2024
0ffd66e
review
Strohgelaender May 18, 2024
1206e7d
Merge remote-tracking branch 'origin/chore/xmlmapper' into chore/xmlm…
Strohgelaender May 18, 2024
c2b2bf3
Merge branch 'develop' into chore/xmlmapper
Strohgelaender May 18, 2024
1d228ca
remove unused class
Strohgelaender May 18, 2024
38fa397
Merge remote-tracking branch 'origin/chore/xmlmapper' into chore/xmlm…
Strohgelaender May 18, 2024
602d164
Merge branch 'develop' into chore/xmlmapper
Strohgelaender May 18, 2024
907673b
Merge branch 'develop' into chore/xmlmapper
Strohgelaender May 18, 2024
e031af1
fix nullpointer exception
Strohgelaender May 18, 2024
b30b2ab
fix parsing of multiple testsuites.
Strohgelaender May 18, 2024
1b6c470
Merge branch 'develop' into chore/xmlmapper
Strohgelaender May 18, 2024
e17c688
Merge branch 'develop' of https://github.com/ls1intum/Artemis into ch…
Strohgelaender May 19, 2024
ef10788
revert run configuration change
Strohgelaender May 19, 2024
e174af0
fix whitespace
Strohgelaender May 19, 2024
c4f08a6
fix yet another npe
Strohgelaender May 19, 2024
bfc4d05
Merge branch 'develop' into chore/xmlmapper
Strohgelaender May 19, 2024
0ae334a
Merge branch 'develop' into chore/xmlmapper
Strohgelaender May 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,8 @@ dependencies {
implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:${fasterxml_version}"
// Support JSON serialization and deserialization of Hibernate (https://hibernate.org) specific data types and properties; especially lazy-loading aspects
implementation "com.fasterxml.jackson.datatype:jackson-datatype-hibernate6:${fasterxml_version}"
// Support XML serialization and deserialization
implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-xml:${fasterxml_version}"

implementation "com.hazelcast:hazelcast:${hazelcast_version}"
implementation "com.hazelcast:hazelcast-spring:${hazelcast_version}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import javax.xml.stream.XMLInputFactory;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
Expand Down Expand Up @@ -143,17 +141,6 @@ public ExecutorService localCIBuildExecutorService() {
return new ThreadPoolExecutor(threadPoolSize, threadPoolSize, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(1), customThreadFactory, customRejectedExecutionHandler);
}

/**
* Creates an XMLInputFactory that is used to parse the test results during execution of the local CI build jobs.
*
* @return The XMLInputFactory bean.
*/
@Bean
public XMLInputFactory localCIXMLInputFactory() {
return XMLInputFactory.newInstance();
}

// TODO: the Artemis server should start even if docker is not running. Also, pulling the image should be done after the start has finished or only on demand
/**
* Creates a Docker client that is used to communicate with the Docker daemon.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
package de.tum.in.www1.artemis.domain.enumeration;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.StringJoiner;

/**
* Enumeration for supported static code analysis tools
Expand Down Expand Up @@ -37,23 +34,6 @@ public String getFilePattern() {
return this.filePattern;
}

public String getArtifactLabel() {
return this.name().toLowerCase();
}

/**
* Returns the artifact labels of all static code analysis tools.
*
* @return Set of static code analysis tool artifact labels
*/
public static Set<String> getAllArtifactLabels() {
Set<String> artifactLabels = new HashSet<>();
for (var tool : StaticCodeAnalysisTool.values()) {
artifactLabels.add(tool.getArtifactLabel());
}
return artifactLabels;
}

/**
* Returns all static code analysis tools supporting the given programming language.
*
Expand Down Expand Up @@ -85,19 +65,4 @@ public static Optional<StaticCodeAnalysisTool> getToolByFilePattern(String fileN
return Optional.empty();
}

/**
* Creates the build plan task command for static code analysis tools of a specific language.
*
* @param language Programming language for which the static code analysis command should be created
* @return the command used to run static code analysis for a specific language
*/
public static String createBuildPlanCommandForProgrammingLanguage(ProgrammingLanguage language) {
StringJoiner commandBuilder = new StringJoiner(" ");
for (var tool : StaticCodeAnalysisTool.values()) {
if (tool.language == language) {
commandBuilder.add(tool.command);
}
}
return commandBuilder.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,13 @@

import static de.tum.in.www1.artemis.config.Constants.PROFILE_CORE;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import org.apache.commons.lang3.tuple.ImmutablePair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Profile;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import de.tum.in.www1.artemis.config.StaticCodeAnalysisConfigurer;
import de.tum.in.www1.artemis.domain.Feedback;
import de.tum.in.www1.artemis.domain.ProgrammingExercise;
import de.tum.in.www1.artemis.domain.Result;
import de.tum.in.www1.artemis.domain.StaticCodeAnalysisCategory;
import de.tum.in.www1.artemis.domain.StaticCodeAnalysisDefaultCategory;
import de.tum.in.www1.artemis.domain.enumeration.CategoryState;
import de.tum.in.www1.artemis.service.dto.StaticCodeAnalysisReportDTO;

/**
* Spring Data repository for the StaticCodeAnalysisCategory entity.
Expand All @@ -36,101 +17,5 @@
@Repository
public interface StaticCodeAnalysisCategoryRepository extends JpaRepository<StaticCodeAnalysisCategory, Long> {

Logger log = LoggerFactory.getLogger(StaticCodeAnalysisCategoryRepository.class);

Set<StaticCodeAnalysisCategory> findByExerciseId(long exerciseId);

@Query("""
SELECT s
FROM StaticCodeAnalysisCategory s
LEFT JOIN FETCH s.exercise
WHERE s.exercise.id = :exerciseId
""")
Set<StaticCodeAnalysisCategory> findWithExerciseByExerciseId(@Param("exerciseId") long exerciseId);

/**
* Links the categories of an exercise with the default category mappings.
*
* @param programmingExercise The programming exercise
* @return A list of pairs of categories and their mappings.
*/
default List<ImmutablePair<StaticCodeAnalysisCategory, List<StaticCodeAnalysisDefaultCategory.CategoryMapping>>> getCategoriesWithMappingForExercise(
ProgrammingExercise programmingExercise) {
var categories = findByExerciseId(programmingExercise.getId());
var defaultCategories = StaticCodeAnalysisConfigurer.staticCodeAnalysisConfiguration().get(programmingExercise.getProgrammingLanguage());

List<ImmutablePair<StaticCodeAnalysisCategory, List<StaticCodeAnalysisDefaultCategory.CategoryMapping>>> categoryPairsWithMapping = new ArrayList<>();

for (var category : categories) {
var defaultCategoryMatch = defaultCategories.stream().filter(defaultCategory -> defaultCategory.name().equals(category.getName())).findFirst();
if (defaultCategoryMatch.isPresent()) {
var categoryMappings = defaultCategoryMatch.get().categoryMappings();
categoryPairsWithMapping.add(new ImmutablePair<>(category, categoryMappings));
}
}

return categoryPairsWithMapping;
}

/**
* Sets the category for each feedback and removes feedback with no category or an inactive one.
* The feedback is removed permanently, which has the advantage that the server or client doesn't have to filter out
* invisible feedback every time it is requested. The drawback is that the re-evaluate functionality can't take
* the removed feedback into account.
*
* @param result of the build run
* @param staticCodeAnalysisFeedback List of static code analysis feedback objects
* @param programmingExercise The current exercise
* @return The filtered list of feedback objects
*/
default List<Feedback> categorizeScaFeedback(Result result, List<Feedback> staticCodeAnalysisFeedback, ProgrammingExercise programmingExercise) {
var categoryPairs = getCategoriesWithMappingForExercise(programmingExercise);

return staticCodeAnalysisFeedback.stream().filter(feedback -> {
// ObjectMapper to extract the static code analysis issue from the feedback
ObjectMapper mapper = new ObjectMapper();
// the category for this feedback
Optional<StaticCodeAnalysisCategory> category = Optional.empty();
try {
// extract the sca issue
var issue = mapper.readValue(feedback.getDetailText(), StaticCodeAnalysisReportDTO.StaticCodeAnalysisIssue.class);

// find the category for this issue
for (var categoryPair : categoryPairs) {
var categoryMappings = categoryPair.right;
if (categoryMappings.stream().anyMatch(mapping -> mapping.tool().name().equals(feedback.getReference()) && mapping.category().equals(issue.getCategory()))) {
category = Optional.of(categoryPair.left);
break;
}
}

if (category.isPresent()) {
if (category.get().getState() == CategoryState.GRADED) {
// update the penalty of the issue
issue.setPenalty(category.get().getPenalty());
}
else if (issue.getPenalty() != null) {
// remove the penalty of the issue
issue.setPenalty(null);
}
// the feedback is already pre-truncated to fit, it should not be shortened further
feedback.setDetailTextTruncated(mapper.writeValueAsString(issue));
}
}
catch (JsonProcessingException exception) {
log.debug("Error occurred parsing feedback {} to static code analysis issue: {}", feedback, exception.getMessage());
}

if (category.isEmpty() || category.get().getState().equals(CategoryState.INACTIVE)) {
// remove feedback in no category or an inactive one
result.removeFeedback(feedback);
return false; // filter this feedback
}
else {
// add the category name to the feedback text
feedback.setText(Feedback.STATIC_CODE_ANALYSIS_FEEDBACK_IDENTIFIER + category.get().getName());
return true; // keep this feedback
}
}).collect(Collectors.toCollection(ArrayList::new));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1003,6 +1003,7 @@ public void switchBackToDefaultBranchHead(Repository repository) throws GitAPIEx
* @return the latestHash of the given repo.
* @throws EntityNotFoundException if retrieving the latestHash from the git repo failed.
*/
@Nullable
Strohgelaender marked this conversation as resolved.
Show resolved Hide resolved
public ObjectId getLastCommitHash(VcsRepositoryUri repoUri) throws EntityNotFoundException {
if (repoUri == null || repoUri.getURI() == null) {
return null;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package de.tum.in.www1.artemis.service.util;
package de.tum.in.www1.artemis.service.connectors.jenkins;

import java.io.IOException;
import java.io.StringReader;
Expand Down Expand Up @@ -26,9 +26,9 @@
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

public class XmlFileUtils {
public class JenkinsXmlFileUtils {

private static final Logger log = LoggerFactory.getLogger(XmlFileUtils.class);
private static final Logger log = LoggerFactory.getLogger(JenkinsXmlFileUtils.class);

public static Document readFromString(String xmlString) {
return parseDocument(xmlString);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import de.tum.in.www1.artemis.service.ResourceLoaderService;
import de.tum.in.www1.artemis.service.connectors.ci.ContinuousIntegrationService;
import de.tum.in.www1.artemis.service.connectors.jenkins.JenkinsXmlConfigBuilder;
import de.tum.in.www1.artemis.service.util.XmlFileUtils;
import de.tum.in.www1.artemis.service.connectors.jenkins.JenkinsXmlFileUtils;

@Profile("jenkins")
@Component
Expand Down Expand Up @@ -99,7 +99,7 @@ public Document buildBasicConfig(final ProgrammingLanguage programmingLanguage,
final Path configFilePath = Path.of("templates", "jenkins", "config.xml");
final var configFileReplacements = Map.of(REPLACE_PIPELINE_SCRIPT, jenkinsfile, REPLACE_PUSH_TOKEN, pushToken);
final var xmlResource = resourceLoaderService.getResource(configFilePath);
return XmlFileUtils.readXmlFile(xmlResource, configFileReplacements);
return JenkinsXmlFileUtils.readXmlFile(xmlResource, configFileReplacements);
}

private String getJenkinsfile(final InternalVcsRepositoryURLs internalVcsRepositoryURLs, final ProgrammingLanguage programmingLanguage, final boolean checkoutSolution,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@
import de.tum.in.www1.artemis.service.connectors.jenkins.JenkinsEndpoints;
import de.tum.in.www1.artemis.service.connectors.jenkins.JenkinsInternalUrlService;
import de.tum.in.www1.artemis.service.connectors.jenkins.JenkinsXmlConfigBuilder;
import de.tum.in.www1.artemis.service.connectors.jenkins.JenkinsXmlFileUtils;
import de.tum.in.www1.artemis.service.connectors.jenkins.jobs.JenkinsJobPermissionsService;
import de.tum.in.www1.artemis.service.connectors.jenkins.jobs.JenkinsJobService;
import de.tum.in.www1.artemis.service.util.XmlFileUtils;

@Service
@Profile("jenkins")
Expand Down Expand Up @@ -263,7 +263,7 @@ private void postBuildPlanConfigChange(String buildPlanKey, String buildProjectK
final var headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_XML);

String jobXmlString = XmlFileUtils.writeToString(jobConfig);
String jobXmlString = JenkinsXmlFileUtils.writeToString(jobConfig);
final var entity = new HttpEntity<>(jobXmlString, headers);

restTemplate.exchange(uri, HttpMethod.POST, entity, String.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import com.offbytwo.jenkins.model.JobWithDetails;

import de.tum.in.www1.artemis.exception.JenkinsException;
import de.tum.in.www1.artemis.service.util.XmlFileUtils;
import de.tum.in.www1.artemis.service.connectors.jenkins.JenkinsXmlFileUtils;

@Service
@Profile("jenkins")
Expand Down Expand Up @@ -101,7 +101,7 @@ public Document getJobConfigForJobInFolder(String folderName, String jobName) {
xmlString = xmlString.replace("*/master", "**");
xmlString = xmlString.replace("*/main", "**");

return XmlFileUtils.readFromString(xmlString);
return JenkinsXmlFileUtils.readFromString(xmlString);
}
catch (IOException e) {
log.error(e.getMessage(), e);
Expand All @@ -122,7 +122,7 @@ public Document getFolderConfig(String folderName) throws IOException {
}

String folderXml = jenkinsServer.getJobXml(folderName);
return XmlFileUtils.readFromString(folderXml);
return JenkinsXmlFileUtils.readFromString(folderXml);
}

/**
Expand All @@ -145,7 +145,7 @@ public void createJobInFolder(Document jobConfig, String folderName, String jobN
return;
}

String configString = XmlFileUtils.writeToString(jobConfig);
String configString = JenkinsXmlFileUtils.writeToString(jobConfig);
jenkinsServer.createJob(folder, jobName, configString, useCrumb);
}
catch (IOException | TransformerException e) {
Expand All @@ -171,7 +171,7 @@ public Document getJobConfig(String folderName, String jobName) throws IOExcepti
var folder = jenkinsServer.getFolderJob(job);

String jobXml = jenkinsServer.getJobXml(folder.orElse(null), jobName);
return XmlFileUtils.readFromString(jobXml);
return JenkinsXmlFileUtils.readFromString(jobXml);
}

/**
Expand All @@ -184,7 +184,7 @@ public Document getJobConfig(String folderName, String jobName) throws IOExcepti
*/
public void updateJob(String folderName, String jobName, Document jobConfig) throws IOException {
try {
String configString = XmlFileUtils.writeToString(jobConfig);
String configString = JenkinsXmlFileUtils.writeToString(jobConfig);

if (folderName != null && !folderName.isEmpty()) {
var job = jenkinsServer.getJob(folderName);
Expand All @@ -209,7 +209,7 @@ public void updateJob(String folderName, String jobName, Document jobConfig) thr
*/
public void updateFolderJob(String folderName, Document folderConfig) throws IOException {
try {
String configString = XmlFileUtils.writeToString(folderConfig);
String configString = JenkinsXmlFileUtils.writeToString(folderConfig);
jenkinsServer.updateJob(folderName, configString, useCrumb);
}
catch (TransformerException e) {
Expand Down
Loading
Loading