diff --git a/marketplace-service/src/main/java/com/axonivy/market/comparator/MavenVersionComparator.java b/marketplace-service/src/main/java/com/axonivy/market/comparator/MavenVersionComparator.java new file mode 100644 index 000000000..d7df9ed85 --- /dev/null +++ b/marketplace-service/src/main/java/com/axonivy/market/comparator/MavenVersionComparator.java @@ -0,0 +1,122 @@ +package com.axonivy.market.comparator; + +import com.axonivy.market.constants.CommonConstants; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; +import org.kohsuke.github.GHTag; +import org.springframework.util.CollectionUtils; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static com.axonivy.market.constants.CommonConstants.DASH_SEPARATOR; +import static com.axonivy.market.constants.MavenConstants.SNAPSHOT_VERSION; +import static org.apache.commons.lang3.StringUtils.EMPTY; + +public class MavenVersionComparator { + + private static final String MAIN_VERSION_REGEX = "\\."; + private static final int GREATER_THAN = 1; + private static final int EQUAL = 0; + private static final int LESS_THAN = -1; + + private MavenVersionComparator() { + } + + public static GHTag findHighestTag(List ghTags) { + if (CollectionUtils.isEmpty(ghTags)) { + return null; + } + String highestVersion = findHighestMavenVersion(ghTags.stream().map(GHTag::getName).toList()); + return ghTags.stream().filter(tag -> tag.getName().equals(highestVersion)).findAny().orElse(null); + } + + public static String findHighestMavenVersion(List versions) { + if (CollectionUtils.isEmpty(versions)) { + return null; + } + + String highestVersion = versions.get(0); + for (var version : versions) { + if (compare(version, highestVersion) > EQUAL) { + highestVersion = version; + } + } + return highestVersion; + } + + private static int compare(String version, String otherVersion) { + version = stripLeadingChars(version); + otherVersion = stripLeadingChars(otherVersion); + String[] versionParts = createMainAndQualifierArray(version); + String[] otherVersionParts = createMainAndQualifierArray(otherVersion); + + // Compare main version parts + int mainComparison = compareMainVersion(versionParts[0], otherVersionParts[0]); + if (mainComparison != EQUAL) { + return mainComparison; + } + + // Compare qualifiers + String qualifier1 = getQualifierPart(versionParts); + String qualifier2 = getQualifierPart(otherVersionParts); + // Consider versions without a qualifier higher than those with qualifiers + if (qualifier1.isEmpty() && !qualifier2.isEmpty()) { + return GREATER_THAN; + } + if (!qualifier1.isEmpty() && qualifier2.isEmpty()) { + return LESS_THAN; + } + return compareQualifier(qualifier1, qualifier2); + } + + private static String stripLeadingChars(String version) { + Pattern pattern = Pattern.compile(CommonConstants.DIGIT_REGEX); + Matcher matcher = pattern.matcher(version); + if (matcher.find()) { + return matcher.group(1); + } + return version; + } + + private static int compareMainVersion(String mainVersion, String otherMainVersion) { + String[] parts1 = mainVersion.split(MAIN_VERSION_REGEX); + String[] parts2 = otherMainVersion.split(MAIN_VERSION_REGEX); + + int length = Math.max(parts1.length, parts2.length); + for (int i = 0; i < length; i++) { + int num1 = parseToNumber(parts1, i); + int num2 = parseToNumber(parts2, i); + if (num1 != num2) { + return num1 - num2; + } + } + return EQUAL; + } + + private static String getQualifierPart(String[] versionParts) { + return versionParts.length > 1 ? versionParts[1] : EMPTY; + } + + private static String[] createMainAndQualifierArray(String version) { + return StringUtils.defaultIfBlank(version, EMPTY).split(DASH_SEPARATOR, 2); + } + + private static int parseToNumber(String[] versionParts, int index) { + if (index < versionParts.length && NumberUtils.isDigits(versionParts[index])) { + return NumberUtils.toInt(versionParts[index]); + } + return 0; + } + + private static int compareQualifier(String qualifier1, String qualifier2) { + if (SNAPSHOT_VERSION.equals(qualifier1) && !SNAPSHOT_VERSION.equals(qualifier2)) { + return LESS_THAN; + } + if (!SNAPSHOT_VERSION.equals(qualifier1) && SNAPSHOT_VERSION.equals(qualifier2)) { + return GREATER_THAN; + } + return qualifier1.compareTo(qualifier2); + } +} diff --git a/marketplace-service/src/main/java/com/axonivy/market/config/AsyncConfig.java b/marketplace-service/src/main/java/com/axonivy/market/config/AsyncConfig.java new file mode 100644 index 000000000..43dcfcdd0 --- /dev/null +++ b/marketplace-service/src/main/java/com/axonivy/market/config/AsyncConfig.java @@ -0,0 +1,24 @@ +package com.axonivy.market.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.AsyncConfigurer; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.Executor; + +@Configuration +public class AsyncConfig implements AsyncConfigurer { + + private static final String THREAD_NAME_PREFIX = "AC-Thread-"; + + @Override + public Executor getAsyncExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(5); + executor.setMaxPoolSize(10); + executor.setQueueCapacity(25); + executor.setThreadNamePrefix(THREAD_NAME_PREFIX); + executor.initialize(); + return executor; + } +} \ No newline at end of file diff --git a/marketplace-service/src/main/java/com/axonivy/market/config/SchedulingConfig.java b/marketplace-service/src/main/java/com/axonivy/market/config/SchedulingConfig.java new file mode 100644 index 000000000..275717f54 --- /dev/null +++ b/marketplace-service/src/main/java/com/axonivy/market/config/SchedulingConfig.java @@ -0,0 +1,20 @@ +package com.axonivy.market.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; + +@Configuration +public class SchedulingConfig { + + private static final String THREAD_NAME_PREFIX = "SC-Thread-"; + + @Bean + public ThreadPoolTaskScheduler taskScheduler() { + ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler(); + taskScheduler.setPoolSize(10); + taskScheduler.setThreadNamePrefix(THREAD_NAME_PREFIX); + taskScheduler.initialize(); + return taskScheduler; + } +} diff --git a/marketplace-service/src/main/java/com/axonivy/market/constants/CommonConstants.java b/marketplace-service/src/main/java/com/axonivy/market/constants/CommonConstants.java index 07d963b75..e21a1aa17 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/constants/CommonConstants.java +++ b/marketplace-service/src/main/java/com/axonivy/market/constants/CommonConstants.java @@ -13,4 +13,5 @@ public class CommonConstants { public static final String DASH_SEPARATOR = "-"; public static final String SPACE_SEPARATOR = " "; public static final String BEARER = "Bearer"; + public static final String DIGIT_REGEX = "([0-9]+.*)"; } diff --git a/marketplace-service/src/main/java/com/axonivy/market/constants/MavenConstants.java b/marketplace-service/src/main/java/com/axonivy/market/constants/MavenConstants.java index 71cdb72ee..67ec50e5a 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/constants/MavenConstants.java +++ b/marketplace-service/src/main/java/com/axonivy/market/constants/MavenConstants.java @@ -4,7 +4,8 @@ public class MavenConstants { private MavenConstants() { } - public static final String SNAPSHOT_RELEASE_POSTFIX = "-SNAPSHOT"; + public static final String SNAPSHOT_VERSION = "SNAPSHOT"; + public static final String SNAPSHOT_RELEASE_POSTFIX = "-" + SNAPSHOT_VERSION; public static final String SPRINT_RELEASE_POSTFIX = "-m"; public static final String PRODUCT_ARTIFACT_POSTFIX = "-product"; public static final String METADATA_URL_FORMAT = "%s/%s/%s/maven-metadata.xml"; diff --git a/marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductServiceImpl.java b/marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductServiceImpl.java index 52deb61d6..afdfd538b 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductServiceImpl.java +++ b/marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductServiceImpl.java @@ -11,13 +11,9 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.security.SecureRandom; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; +import java.util.*; +import com.axonivy.market.comparator.MavenVersionComparator; import com.axonivy.market.util.VersionUtils; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.ObjectUtils; @@ -375,14 +371,11 @@ private void getProductContents(Product product) { private void updateProductFromReleaseTags(Product product, GHRepository productRepo) { List productModuleContents = new ArrayList<>(); List ghTags = getProductReleaseTags(product); - GHTag lastTag = CollectionUtils.firstElement(ghTags); - + GHTag lastTag = MavenVersionComparator.findHighestTag(ghTags); if (lastTag == null || lastTag.getName().equals(product.getNewestReleaseVersion())) { return; } - - getPublishedDateFromLatestTag(product, - lastTag); + product.setNewestPublishedDate(getPublishedDateFromLatestTag(lastTag)); product.setNewestReleaseVersion(lastTag.getName()); if (!CollectionUtils.isEmpty(product.getReleasedVersions())) { @@ -407,12 +400,13 @@ private void updateProductFromReleaseTags(Product product, GHRepository productR } } - private void getPublishedDateFromLatestTag(Product product, GHTag lastTag) { + private Date getPublishedDateFromLatestTag(GHTag lastTag) { try { - product.setNewestPublishedDate(lastTag.getCommit().getCommitDate()); - } catch (IOException e) { + return lastTag.getCommit().getCommitDate(); + } catch (Exception e) { log.error("Fail to get commit date ", e); } + return null; } private void updateProductCompatibility(Product product) { @@ -427,13 +421,12 @@ private void updateProductCompatibility(Product product) { } private List getProductReleaseTags(Product product) { - List tags = new ArrayList<>(); try { - tags = gitHubService.getRepositoryTags(product.getRepositoryName()); + return gitHubService.getRepositoryTags(product.getRepositoryName()); } catch (IOException e) { log.error("Cannot get tag list of product ", e); } - return tags; + return List.of(); } // Cover 3 cases after removing non-numeric characters (8, 11.1 and 10.0.2) diff --git a/marketplace-service/src/test/java/com/axonivy/market/service/impl/ProductServiceImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/service/impl/ProductServiceImplTest.java index 29aeb5faa..8c9e4e1ac 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/service/impl/ProductServiceImplTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/service/impl/ProductServiceImplTest.java @@ -380,10 +380,10 @@ void testSyncProductsSecondTime() throws IOException { GHTag mockTag = mock(GHTag.class); when(mockTag.getName()).thenReturn("v10.0.2"); - when(mockTag.getCommit()).thenReturn(mockGHCommit); GHTag mockTag2 = mock(GHTag.class); when(mockTag2.getName()).thenReturn("v10.0.3"); + when(mockTag2.getCommit()).thenReturn(mockGHCommit); when(mockGHCommit.getCommitDate()).thenReturn(new Date()); when(gitHubService.getRepositoryTags(anyString())).thenReturn(Arrays.asList(mockTag, mockTag2));