diff --git a/marketplace-service/src/main/java/com/axonivy/market/controller/ProductController.java b/marketplace-service/src/main/java/com/axonivy/market/controller/ProductController.java index 12ecba55a..e6d730477 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/controller/ProductController.java +++ b/marketplace-service/src/main/java/com/axonivy/market/controller/ProductController.java @@ -116,6 +116,10 @@ public ResponseEntity syncProducts(@RequestHeader(value = AUTHORIZATION return new ResponseEntity<>(message, HttpStatus.OK); } + /** + * @deprecated + */ + @Deprecated(forRemoval = true , since = "1.6.0") @PutMapping(SYNC_PRODUCT_VERSION) @Operation(hidden = true) public ResponseEntity syncProductVersions(@RequestHeader(value = AUTHORIZATION) String authorizationHeader diff --git a/marketplace-service/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyProductRepoServiceImpl.java b/marketplace-service/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyProductRepoServiceImpl.java index e5d79ee4c..ae3285ca8 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyProductRepoServiceImpl.java +++ b/marketplace-service/src/main/java/com/axonivy/market/github/service/impl/GHAxonIvyProductRepoServiceImpl.java @@ -4,12 +4,15 @@ import com.axonivy.market.constants.ReadmeConstants; import com.axonivy.market.entity.Product; import com.axonivy.market.entity.ProductModuleContent; +import com.axonivy.market.enums.Language; import com.axonivy.market.github.service.GHAxonIvyProductRepoService; import com.axonivy.market.github.service.GitHubService; import com.axonivy.market.github.util.GitHubUtils; +import com.axonivy.market.model.ReadmeContentsModel; import com.axonivy.market.service.ImageService; import com.axonivy.market.util.ProductContentUtils; import lombok.extern.log4j.Log4j2; +import org.apache.commons.lang3.StringUtils; import org.kohsuke.github.GHContent; import org.kohsuke.github.GHOrganization; import org.springframework.stereotype.Service; @@ -23,6 +26,7 @@ import java.util.Optional; import static com.axonivy.market.constants.CommonConstants.IMAGE_ID_PREFIX; +import static com.axonivy.market.util.ProductContentUtils.*; @Log4j2 @Service @@ -66,7 +70,10 @@ public void extractReadMeFileFromContents(Product product, List conte if (ProductContentUtils.hasImageDirectives(readmeContents)) { readmeContents = updateImagesWithDownloadUrl(product.getId(), contents, readmeContents); } - ProductContentUtils.getExtractedPartsOfReadme(moduleContents, readmeContents, readmeFile.getName()); + + ReadmeContentsModel readmeContentsModel = ProductContentUtils.getExtractedPartsOfReadme(readmeContents); + + ProductContentUtils.mappingDescriptionSetupAndDemo(moduleContents, readmeFile.getName(), readmeContentsModel); } ProductContentUtils.updateProductModuleTabContents(productModuleContent, moduleContents); } diff --git a/marketplace-service/src/main/java/com/axonivy/market/model/ReadmeContentsModel.java b/marketplace-service/src/main/java/com/axonivy/market/model/ReadmeContentsModel.java new file mode 100644 index 000000000..4f7173dd8 --- /dev/null +++ b/marketplace-service/src/main/java/com/axonivy/market/model/ReadmeContentsModel.java @@ -0,0 +1,16 @@ +package com.axonivy.market.model; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class ReadmeContentsModel { + private String description; + private String demo; + private String setup; +} diff --git a/marketplace-service/src/main/java/com/axonivy/market/repository/ProductJsonContentRepository.java b/marketplace-service/src/main/java/com/axonivy/market/repository/ProductJsonContentRepository.java index 428d1b631..d67763eec 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/repository/ProductJsonContentRepository.java +++ b/marketplace-service/src/main/java/com/axonivy/market/repository/ProductJsonContentRepository.java @@ -11,5 +11,7 @@ public interface ProductJsonContentRepository extends MongoRepository findByProductIdAndVersion(String productId, String version); + List findByProductIdAndVersionIn(String productId, List versions); + void deleteAllByProductId(String productId); } diff --git a/marketplace-service/src/main/java/com/axonivy/market/schedulingtask/ScheduledTasks.java b/marketplace-service/src/main/java/com/axonivy/market/schedulingtask/ScheduledTasks.java index 3a3d63bb2..078ba2fdd 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/schedulingtask/ScheduledTasks.java +++ b/marketplace-service/src/main/java/com/axonivy/market/schedulingtask/ScheduledTasks.java @@ -2,7 +2,6 @@ import com.axonivy.market.repository.ProductRepository; import com.axonivy.market.service.ExternalDocumentService; -import com.axonivy.market.service.MetadataService; import com.axonivy.market.service.ProductService; import lombok.AllArgsConstructor; import lombok.extern.log4j.Log4j2; @@ -15,15 +14,12 @@ public class ScheduledTasks { private static final String SCHEDULING_TASK_PRODUCTS_CRON = "0 0 0/1 ? * *"; - // Maven version sync will start at 00:20 in order to prevent running at the same time with product repo sync - private static final String SCHEDULING_TASK_MAVEN_VERSION_CRON = "0 20 0 * * *"; // External documentation sync will start at 00:40 in order to prevent running at the same time with other private static final String SCHEDULING_TASK_DOCUMENTS_CRON = "0 40 0 * * *"; final ProductRepository productRepo; final ProductService productService; final ExternalDocumentService externalDocumentService; - private final MetadataService metadataService; @Scheduled(cron = SCHEDULING_TASK_PRODUCTS_CRON) public void syncDataForProductFromGitHubRepo() { @@ -31,11 +27,6 @@ public void syncDataForProductFromGitHubRepo() { productService.syncLatestDataFromMarketRepo(false); } - @Scheduled(cron = SCHEDULING_TASK_MAVEN_VERSION_CRON) - public void syncDataForMavenMetadata() { - log.warn("Started sync data for Maven metadata"); - metadataService.syncAllProductsMetadata(); - } @Scheduled(cron = SCHEDULING_TASK_DOCUMENTS_CRON) public void syncDataForProductDocuments() { log.warn("Started sync data for product document"); diff --git a/marketplace-service/src/main/java/com/axonivy/market/service/MetadataService.java b/marketplace-service/src/main/java/com/axonivy/market/service/MetadataService.java index bbb275f2e..6389c07c2 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/service/MetadataService.java +++ b/marketplace-service/src/main/java/com/axonivy/market/service/MetadataService.java @@ -1,9 +1,14 @@ package com.axonivy.market.service; +import com.axonivy.market.bo.Artifact; import com.axonivy.market.entity.Product; +import java.util.List; public interface MetadataService { int syncAllProductsMetadata(); + boolean syncProductMetadata(Product product); + + void updateArtifactAndMetadata(String productId , List versions , List artifacts); } diff --git a/marketplace-service/src/main/java/com/axonivy/market/service/ProductJsonContentService.java b/marketplace-service/src/main/java/com/axonivy/market/service/ProductJsonContentService.java index 4edfacacf..ef8e61425 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/service/ProductJsonContentService.java +++ b/marketplace-service/src/main/java/com/axonivy/market/service/ProductJsonContentService.java @@ -1,6 +1,8 @@ package com.axonivy.market.service; +import com.axonivy.market.entity.ProductJsonContent; + public interface ProductJsonContentService { - void updateProductJsonContent(String jsonContent, String currentVersion, String replaceVersion, + ProductJsonContent updateProductJsonContent(String jsonContent, String currentVersion, String replaceVersion, String productId, String productName); } \ No newline at end of file diff --git a/marketplace-service/src/main/java/com/axonivy/market/service/impl/MetadataServiceImpl.java b/marketplace-service/src/main/java/com/axonivy/market/service/impl/MetadataServiceImpl.java index a75585d32..353807a69 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/service/impl/MetadataServiceImpl.java +++ b/marketplace-service/src/main/java/com/axonivy/market/service/impl/MetadataServiceImpl.java @@ -127,28 +127,65 @@ public boolean syncProductMetadata(Product product) { return true; } + @Override + public void updateArtifactAndMetadata(String productId, List versions, List artifacts) { + Set metadataSet = new HashSet<>(metadataRepo.findByProductId(productId)); + Set artifactsFromNewVersions = new HashSet<>(); + + if (ObjectUtils.isNotEmpty(versions)) { + List productJsonContents = productJsonRepo.findByProductIdAndVersionIn(productId, versions); + for (ProductJsonContent productJsonContent : productJsonContents) { + List artifactsFromNonSyncedVersions = MavenUtils.getMavenArtifactsFromProductJson(productJsonContent); + artifactsFromNewVersions.addAll(artifactsFromNonSyncedVersions); + } + log.info("**MetadataService: New versions detected: {} in product {}", versions, productId); + } + + metadataSet.addAll(MavenUtils.convertArtifactsToMetadataSet(artifactsFromNewVersions, productId)); + if (ObjectUtils.isNotEmpty(artifacts)) { + metadataSet.addAll(MavenUtils.convertArtifactsToMetadataSet(new HashSet<>(artifacts), productId)); + } + + if (CollectionUtils.isEmpty(metadataSet)) { + log.info("**MetadataService: No artifact found in product {}", productId); + return; + } + + MavenArtifactVersion artifactVersion = mavenArtifactVersionRepo.findById(productId) + .orElse(MavenArtifactVersion.builder().productId(productId).build()); + + artifactVersion.setAdditionalArtifactsByVersion(new HashMap<>()); + updateMavenArtifactVersionData(metadataSet, artifactVersion); + + mavenArtifactVersionRepo.save(artifactVersion); + metadataRepo.saveAll(metadataSet); + } + public void updateMavenArtifactVersionFromMetadata(MavenArtifactVersion artifactVersionCache, Metadata metadata) { // Skip to add new model for product artifact if (MavenUtils.isProductArtifactId(metadata.getArtifactId())) { return; } - metadata.getVersions().forEach(version -> { - if (VersionUtils.isSnapshotVersion(version)) { - if (VersionUtils.isOfficialVersionOrUnReleasedDevVersion(metadata.getVersions().stream().toList(), version)) { - updateMavenArtifactVersionForNonReleaseDevVersion(artifactVersionCache, metadata, version); - } - } else { + + for (String version : metadata.getVersions()) { + boolean isSnapshotVersion = VersionUtils.isSnapshotVersion(version); + boolean isOfficialVersionOrUnReleasedDevVersion = + VersionUtils.isOfficialVersionOrUnReleasedDevVersion(metadata.getVersions(), version); + + if (isSnapshotVersion && isOfficialVersionOrUnReleasedDevVersion) { + updateMavenArtifactVersionForNonReleaseDevVersion(artifactVersionCache, metadata, version); + } else if (!isSnapshotVersion) { updateMavenArtifactVersionCacheWithModel(artifactVersionCache, version, metadata); } - }); + } } public void updateMavenArtifactVersionForNonReleaseDevVersion(MavenArtifactVersion artifactVersionCache, Metadata metadata, String version) { Metadata snapShotMetadata = MavenUtils.buildSnapShotMetadataFromVersion(metadata, version); - MetadataReaderUtils.updateMetadataFromMavenXML(MavenUtils.getMetadataContentFromUrl(snapShotMetadata.getUrl()), - snapShotMetadata, true); + String xmlDataForSnapshotMetadata = MavenUtils.getMetadataContentFromUrl(snapShotMetadata.getUrl()); + MetadataReaderUtils.updateMetadataFromMavenXML(xmlDataForSnapshotMetadata, snapShotMetadata, true); updateMavenArtifactVersionCacheWithModel(artifactVersionCache, version, snapShotMetadata); } @@ -157,12 +194,13 @@ public Set getArtifactsFromNonSyncedVersion(String productId, List { - ProductJsonContent productJson = - productJsonRepo.findByProductIdAndVersion(productId, version).stream().findAny().orElse(null); - List artifactsInVersion = MavenUtils.getMavenArtifactsFromProductJson(productJson); + + List productJsonContents = productJsonRepo.findByProductIdAndVersionIn(productId, nonSyncedVersions); + for (ProductJsonContent productJsonContent : productJsonContents) { + List artifactsInVersion = MavenUtils.getMavenArtifactsFromProductJson(productJsonContent); artifacts.addAll(artifactsInVersion); - }); + } + return artifacts; } } diff --git a/marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductContentServiceImpl.java b/marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductContentServiceImpl.java index 51061e6f8..d7dd11adf 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductContentServiceImpl.java +++ b/marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductContentServiceImpl.java @@ -4,7 +4,9 @@ import com.axonivy.market.constants.CommonConstants; import com.axonivy.market.constants.ProductJsonConstants; import com.axonivy.market.constants.ReadmeConstants; +import com.axonivy.market.entity.Image; import com.axonivy.market.entity.ProductModuleContent; +import com.axonivy.market.model.ReadmeContentsModel; import com.axonivy.market.service.FileDownloadService; import com.axonivy.market.service.ImageService; import com.axonivy.market.service.ProductContentService; @@ -13,7 +15,6 @@ import com.axonivy.market.util.ProductContentUtils; import lombok.AllArgsConstructor; import lombok.extern.log4j.Log4j2; -import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.util.Strings; import org.springframework.stereotype.Service; @@ -39,8 +40,7 @@ public class ProductContentServiceImpl implements ProductContentService { @Override public ProductModuleContent getReadmeAndProductContentsFromVersion(String productId, String version, String url, Artifact artifact, String productName) { - ProductModuleContent productModuleContent = ProductContentUtils.initProductModuleContent(productId, - version); + ProductModuleContent productModuleContent = ProductContentUtils.initProductModuleContent(productId, version); String unzippedFolderPath = Strings.EMPTY; try { unzippedFolderPath = fileDownloadService.downloadAndUnzipProductContentFile(url, artifact); @@ -59,54 +59,60 @@ public ProductModuleContent getReadmeAndProductContentsFromVersion(String produc public void updateDependencyContentsFromProductJson(ProductModuleContent productModuleContent, String productId, String unzippedFolderPath, String productName) throws IOException { - List artifacts = MavenUtils.convertProductJsonToMavenProductInfo( - Paths.get(unzippedFolderPath)); + List artifacts = MavenUtils.convertProductJsonToMavenProductInfo(Paths.get(unzippedFolderPath)); ProductContentUtils.updateProductModule(productModuleContent, artifacts); Path productJsonPath = Paths.get(unzippedFolderPath, ProductJsonConstants.PRODUCT_JSON_FILE); String content = MavenUtils.extractProductJsonContent(productJsonPath); + productJsonContentService.updateProductJsonContent(content, productModuleContent.getVersion(), ProductJsonConstants.VERSION_VALUE, productId, productName); } private void extractReadMeFileFromContents(String productId, String unzippedFolderPath, ProductModuleContent productModuleContent) { - try { - List readmeFiles; - Map> moduleContents = new HashMap<>(); - try (Stream readmePathStream = Files.walk(Paths.get(unzippedFolderPath))) { - readmeFiles = readmePathStream.filter(Files::isRegularFile).filter( - path -> path.getFileName().toString().startsWith(ReadmeConstants.README_FILE_NAME)).toList(); - } - if (ObjectUtils.isNotEmpty(readmeFiles)) { - for (Path readmeFile : readmeFiles) { - String readmeContents = Files.readString(readmeFile); - if (ProductContentUtils.hasImageDirectives(readmeContents)) { - readmeContents = updateImagesWithDownloadUrl(productId, unzippedFolderPath, readmeContents); - } - ProductContentUtils.getExtractedPartsOfReadme(moduleContents, readmeContents, - readmeFile.getFileName().toString()); + Map> moduleContents = new HashMap<>(); + try (Stream readmePathStream = Files.walk(Paths.get(unzippedFolderPath))){ + List readmeFiles = readmePathStream.filter(Files::isRegularFile) + .filter(path -> path.getFileName().toString().startsWith(ReadmeConstants.README_FILE_NAME)) + .toList(); + + for (Path readmeFile : readmeFiles) { + String readmeContents = Files.readString(readmeFile); + if (ProductContentUtils.hasImageDirectives(readmeContents)) { + readmeContents = updateImagesWithDownloadUrl(productId, unzippedFolderPath, readmeContents); } - ProductContentUtils.updateProductModuleTabContents(productModuleContent, moduleContents); + + ReadmeContentsModel readmeContentsModel = ProductContentUtils.getExtractedPartsOfReadme(readmeContents); + + ProductContentUtils.mappingDescriptionSetupAndDemo(moduleContents, readmeFile.getFileName().toString(), + readmeContentsModel); } - } catch (Exception e) { + ProductContentUtils.updateProductModuleTabContents(productModuleContent, moduleContents); + } catch (IOException e) { log.error("Cannot get README file's content from folder {}: {}", unzippedFolderPath, e.getMessage()); } } public String updateImagesWithDownloadUrl(String productId, String unzippedFolderPath, - String readmeContents) throws IOException { - List allImagePaths; + String readmeContents) { Map imageUrls = new HashMap<>(); try (Stream imagePathStream = Files.walk(Paths.get(unzippedFolderPath))) { - allImagePaths = imagePathStream.filter(Files::isRegularFile).filter( + List allImagePaths = imagePathStream.filter(Files::isRegularFile).filter( path -> path.getFileName().toString().toLowerCase().matches(CommonConstants.IMAGE_EXTENSION)).toList(); - } - for (Path imagePath : allImagePaths) { - Optional.of(imageService.mappingImageFromDownloadedFolder(productId, imagePath)).ifPresent( - image -> imageUrls.put(imagePath.getFileName().toString(), - CommonConstants.IMAGE_ID_PREFIX.concat(image.getId()))); - } - return ProductContentUtils.replaceImageDirWithImageCustomId(imageUrls, readmeContents); + for (Path imagePath : allImagePaths) { + Image currentImage = imageService.mappingImageFromDownloadedFolder(productId, imagePath); + Optional.ofNullable(currentImage).ifPresent(image -> { + String imageFileName = imagePath.getFileName().toString(); + String imageIdFormat = CommonConstants.IMAGE_ID_PREFIX.concat(image.getId()); + imageUrls.put(imageFileName, imageIdFormat); + }); + } + + return ProductContentUtils.replaceImageDirWithImageCustomId(imageUrls, readmeContents); + } catch (Exception e) { + log.error(e.getMessage()); + } + return readmeContents; } } diff --git a/marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductJsonContentServiceImpl.java b/marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductJsonContentServiceImpl.java index 6b0a2dc48..55506ad74 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductJsonContentServiceImpl.java +++ b/marketplace-service/src/main/java/com/axonivy/market/service/impl/ProductJsonContentServiceImpl.java @@ -14,7 +14,7 @@ public class ProductJsonContentServiceImpl implements ProductJsonContentService private final ProductJsonContentRepository productJsonRepo; @Override - public void updateProductJsonContent(String jsonContent, String currentVersion, String replaceVersion, + public ProductJsonContent updateProductJsonContent(String jsonContent, String currentVersion, String replaceVersion, String productId, String productName) { if (ObjectUtils.isNotEmpty(jsonContent)) { ProductJsonContent productJsonContent = new ProductJsonContent(); @@ -23,7 +23,8 @@ public void updateProductJsonContent(String jsonContent, String currentVersion, ProductFactory.mappingIdForProductJsonContent(productJsonContent); productJsonContent.setName(productName); productJsonContent.setContent(jsonContent.replace(replaceVersion, currentVersion)); - productJsonRepo.save(productJsonContent); + return productJsonRepo.save(productJsonContent); } + return null; } } 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 30f07264e..df1d19ced 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 @@ -83,7 +83,6 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; -import java.util.function.Predicate; import static com.axonivy.market.constants.CommonConstants.SLASH; import static com.axonivy.market.constants.MavenConstants.*; @@ -114,10 +113,10 @@ public class ProductServiceImpl implements ProductService { private final ImageRepository imageRepo; private final ImageService imageService; private final MongoTemplate mongoTemplate; - private final MetadataService metadataService; private final ProductContentService productContentService; private final ObjectMapper mapper = new ObjectMapper(); private final SecureRandom random = new SecureRandom(); + private final MetadataService metadataService; private GHCommit lastGHCommit; private GitHubRepoMeta marketRepoMeta; @Value("${market.legacy.installation.counts.path}") @@ -130,9 +129,9 @@ public ProductServiceImpl(ProductRepository productRepo, GHAxonIvyMarketRepoService axonIvyMarketRepoService, GHAxonIvyProductRepoService axonIvyProductRepoService, GitHubRepoMetaRepository gitHubRepoMetaRepo, GitHubService gitHubService, ProductCustomSortRepository productCustomSortRepo, MavenArtifactVersionRepository mavenArtifactVersionRepo, - ProductJsonContentRepository productJsonContentRepo, ImageRepository imageRepo, MetadataService metadataService, + ProductJsonContentRepository productJsonContentRepo, ImageRepository imageRepo, MetadataSyncRepository metadataSyncRepo, MetadataRepository metadataRepo, ImageService imageService, - MongoTemplate mongoTemplate, ProductContentService productContentService) { + MongoTemplate mongoTemplate, ProductContentService productContentService, MetadataService metadataService) { this.productRepo = productRepo; this.productModuleContentRepo = productModuleContentRepo; this.axonIvyMarketRepoService = axonIvyMarketRepoService; @@ -144,11 +143,11 @@ public ProductServiceImpl(ProductRepository productRepo, this.productJsonContentRepo = productJsonContentRepo; this.metadataSyncRepo = metadataSyncRepo; this.metadataRepo = metadataRepo; - this.metadataService = metadataService; this.imageRepo = imageRepo; this.imageService = imageService; this.mongoTemplate = mongoTemplate; this.productContentService = productContentService; + this.metadataService = metadataService; } @Override @@ -485,30 +484,32 @@ private void updateProductFromReleasedVersions(Product product) { mavenArtifacts.addAll(productArtifacts); mavenArtifacts.addAll(archivedArtifacts); + List nonSyncReleasedVersions = new ArrayList<>(); for (Artifact mavenArtifact : mavenArtifacts) { - getMetadataContent(mavenArtifact, product); + getMetadataContent(mavenArtifact, product, nonSyncReleasedVersions); } + metadataService.updateArtifactAndMetadata(product.getId(), nonSyncReleasedVersions, product.getArtifacts()); } - private void getMetadataContent(Artifact artifact, Product product) { + private void getMetadataContent(Artifact artifact, Product product, List nonSyncReleasedVersions) { String metadataUrl = MavenUtils.buildMetadataUrlFromArtifactInfo(artifact.getRepoUrl(), artifact.getGroupId(), createProductArtifactId(artifact)); String metadataContent = MavenUtils.getMetadataContentFromUrl(metadataUrl); if (StringUtils.isNotBlank(metadataContent)) { - updateContentsFromMavenXML(product, metadataContent, artifact); + updateContentsFromMavenXML(product, metadataContent, artifact, nonSyncReleasedVersions); } } - private void updateContentsFromMavenXML(Product product, String metadataContent, Artifact mavenArtifact) { + private void updateContentsFromMavenXML(Product product, String metadataContent, Artifact mavenArtifact, List nonSyncReleasedVersions) { Document document = MetadataReaderUtils.getDocumentFromXMLContent(metadataContent); String latestVersion = MetadataReaderUtils.getElementValue(document, MavenConstants.LATEST_VERSION_TAG); if (StringUtils.equals(latestVersion, product.getNewestReleaseVersion())) { return; } + product.setNewestPublishedDate(getNewestPublishedDate(document)); product.setNewestReleaseVersion(latestVersion); - NodeList versionNodes = document.getElementsByTagName(MavenConstants.VERSION_TAG); List mavenVersions = new ArrayList<>(); for (int i = 0; i < versionNodes.getLength(); i++) { @@ -517,19 +518,23 @@ private void updateContentsFromMavenXML(Product product, String metadataContent, updateProductCompatibility(product, mavenVersions); - List currentVersions = product.getReleasedVersions(); - if (CollectionUtils.isEmpty(currentVersions)) { - product.setReleasedVersions(new ArrayList<>()); - currentVersions = productModuleContentRepo.findVersionsByProductId(product.getId()); - } - mavenVersions = mavenVersions.stream().filter(filterNonPersistVersion(currentVersions)).toList(); + List currentVersions = ObjectUtils.isNotEmpty(product.getReleasedVersions()) ? + product.getReleasedVersions() : + productModuleContentRepo.findVersionsByProductId(product.getId()); + + mavenVersions = mavenVersions.stream().filter(version -> !currentVersions.contains(version)).toList(); + + Optional.ofNullable(product.getReleasedVersions()).ifPresentOrElse(releasedVersion -> {}, + () -> product.setReleasedVersions(new ArrayList<>())); List productModuleContents = new ArrayList<>(); for (String version : mavenVersions) { product.getReleasedVersions().add(version); - handleProductArtifact(version, product.getId(), productModuleContents, mavenArtifact, + ProductModuleContent productModuleContent = handleProductArtifact(version, product.getId(), mavenArtifact, product.getNames().get(EN_LANGUAGE)); + Optional.ofNullable(productModuleContent).ifPresent(productModuleContents::add); } + nonSyncReleasedVersions.addAll(mavenVersions); if (ObjectUtils.isNotEmpty(productModuleContents)) { productModuleContentRepo.saveAll(productModuleContents); @@ -554,12 +559,8 @@ private void updateProductCompatibility(Product product, List mavenVersi } } - private static Predicate filterNonPersistVersion(List currentVersions) { - return version -> !currentVersions.contains(version); - } - - public void handleProductArtifact(String version, String productId, - List productModuleContents, Artifact mavenArtifact, String productName) { + public ProductModuleContent handleProductArtifact(String version, String productId, Artifact mavenArtifact, + String productName) { String snapshotVersionValue = Strings.EMPTY; if (version.contains(MavenConstants.SNAPSHOT_VERSION)) { snapshotVersionValue = MetadataReaderUtils.getSnapshotVersionValue(version, mavenArtifact); @@ -571,15 +572,8 @@ public void handleProductArtifact(String version, String productId, String url = MavenUtils.buildDownloadUrl(artifactId, version, type, repoUrl, mavenArtifact.getGroupId(), StringUtils.defaultIfBlank(snapshotVersionValue, version)); - if (StringUtils.isBlank(url)) { - return; - } - - try { - addProductContent(productId, version, url, productModuleContents, mavenArtifact, productName); - } catch (Exception e) { - log.error("Cannot download and unzip file {}", e.getMessage()); - } + return productContentService.getReadmeAndProductContentsFromVersion(productId, + version, url, mavenArtifact, productName); } private String createProductArtifactId(Artifact mavenArtifact) { @@ -587,14 +581,6 @@ private String createProductArtifactId(Artifact mavenArtifact) { : mavenArtifact.getArtifactId().concat(PRODUCT_ARTIFACT_POSTFIX); } - public void addProductContent(String productId, String version, String url, - List productModuleContents, Artifact artifact, String productName) { - ProductModuleContent productModuleContent = productContentService.getReadmeAndProductContentsFromVersion(productId, - version, url, artifact, productName); - if (Objects.nonNull(productModuleContent)) { - productModuleContents.add(productModuleContent); - } - } // Cover 3 cases after removing non-numeric characters (8, 11.1 and 10.0.2) @Override @@ -731,7 +717,6 @@ public boolean syncOneProduct(String productId, String marketItemPath, Boolean o updateProductContentForNonStandardProduct(gitHubContents, product); updateProductFromReleasedVersions(product); productRepo.save(product); - metadataService.syncProductMetadata(product); log.info("Sync product {} is finished!", productId); return true; } diff --git a/marketplace-service/src/main/java/com/axonivy/market/util/ProductContentUtils.java b/marketplace-service/src/main/java/com/axonivy/market/util/ProductContentUtils.java index 9d63d6fe1..43821814c 100644 --- a/marketplace-service/src/main/java/com/axonivy/market/util/ProductContentUtils.java +++ b/marketplace-service/src/main/java/com/axonivy/market/util/ProductContentUtils.java @@ -7,6 +7,7 @@ import com.axonivy.market.entity.ProductModuleContent; import com.axonivy.market.enums.Language; import com.axonivy.market.factory.ProductFactory; +import com.axonivy.market.model.ReadmeContentsModel; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.util.Strings; @@ -14,6 +15,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -57,9 +59,7 @@ public static String getReadmeFileLocale(String readmeFile) { // Cover some cases including when demo and setup parts switch positions or // missing one of them - public static void getExtractedPartsOfReadme(Map> moduleContents, String readmeContents, - String readmeFileName) { - String locale = getReadmeFileLocale(readmeFileName); + public static ReadmeContentsModel getExtractedPartsOfReadme( String readmeContents) { String[] parts = readmeContents.split(DEMO_SETUP_TITLE); int demoIndex = readmeContents.indexOf(ReadmeConstants.DEMO_PART); int setupIndex = readmeContents.indexOf(ReadmeConstants.SETUP_PART); @@ -84,15 +84,13 @@ public static void getExtractedPartsOfReadme(Map> mo } else if (setupIndex != -1) { setup = parts[1]; } - locale = StringUtils.isEmpty(locale) ? Language.EN.getValue() : locale.toLowerCase(); - addLocaleContent(moduleContents, DESCRIPTION, description.trim(), locale); - addLocaleContent(moduleContents, DEMO, demo.trim(), locale); - addLocaleContent(moduleContents, SETUP, setup.trim(), locale); - } - public static void addLocaleContent(Map> moduleContents, String type, String content, - String locale) { - moduleContents.computeIfAbsent(type, key -> new HashMap<>()).put(locale, content); + ReadmeContentsModel readmeContentsModel = new ReadmeContentsModel(); + readmeContentsModel.setDescription(description.trim()); + readmeContentsModel.setDemo(demo.trim()); + readmeContentsModel.setSetup(setup.trim()); + + return readmeContentsModel; } public static boolean hasImageDirectives(String readmeContents) { @@ -136,11 +134,9 @@ public static void updateProductModule(ProductModuleContent productModuleContent public static void updateProductModuleTabContents(ProductModuleContent productModuleContent, Map> moduleContents) { - productModuleContent.setDescription( - replaceEmptyContentsWithEnContent(moduleContents.get(DESCRIPTION))); + productModuleContent.setDescription(replaceEmptyContentsWithEnContent(moduleContents.get(DESCRIPTION))); productModuleContent.setDemo(replaceEmptyContentsWithEnContent(moduleContents.get(DEMO))); - productModuleContent.setSetup(replaceEmptyContentsWithEnContent(moduleContents.get - (SETUP))); + productModuleContent.setSetup(replaceEmptyContentsWithEnContent(moduleContents.get(SETUP))); } public static String replaceImageDirWithImageCustomId(Map imageUrls, String readmeContents) { @@ -151,4 +147,17 @@ public static String replaceImageDirWithImageCustomId(Map imageU } return readmeContents; } + + public static void mappingDescriptionSetupAndDemo(Map> moduleContents, + String readmeFileName, ReadmeContentsModel readmeContentsModel) { + String locale = Optional.ofNullable(getReadmeFileLocale(readmeFileName)) + .filter(StringUtils::isNotEmpty) + .map(String::toLowerCase) + .orElse(Language.EN.getValue()); + + moduleContents.computeIfAbsent(DESCRIPTION, key -> new HashMap<>()).put(locale, + readmeContentsModel.getDescription()); + moduleContents.computeIfAbsent(SETUP, key -> new HashMap<>()).put(locale, readmeContentsModel.getSetup()); + moduleContents.computeIfAbsent(DEMO, key -> new HashMap<>()).put(locale, readmeContentsModel.getDemo()); + } } diff --git a/marketplace-service/src/test/java/com/axonivy/market/service/impl/GHAxonIvyProductRepoServiceImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/service/impl/GHAxonIvyProductRepoServiceImplTest.java index 209e7f236..3d3815daf 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/service/impl/GHAxonIvyProductRepoServiceImplTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/service/impl/GHAxonIvyProductRepoServiceImplTest.java @@ -8,6 +8,7 @@ import com.axonivy.market.github.service.GitHubService; import com.axonivy.market.github.service.impl.GHAxonIvyProductRepoServiceImpl; import com.axonivy.market.github.util.GitHubUtils; +import com.axonivy.market.model.ReadmeContentsModel; import com.axonivy.market.service.ImageService; import com.axonivy.market.util.MavenUtils; import com.axonivy.market.util.ProductContentUtils; @@ -35,15 +36,12 @@ import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -221,10 +219,11 @@ Setup content (imageId-66e2b14868f2f95b2f95549a)""", @Test void testExtractReadMeFileFromContents() throws IOException { try (MockedStatic mockedProductContentUtils = Mockito.mockStatic(ProductContentUtils.class)) { + String mockReadmeContent = "Test README content"; GHContent mockReadmeFile = mock(GHContent.class); when(mockReadmeFile.isFile()).thenReturn(true); when(mockReadmeFile.getName()).thenReturn("README.md"); - when(mockReadmeFile.read()).thenReturn(new ByteArrayInputStream("Test README content".getBytes())); + when(mockReadmeFile.read()).thenReturn(new ByteArrayInputStream(mockReadmeContent.getBytes())); GHContent mockImageFile = mock(GHContent.class); when(mockImageFile.isFile()).thenReturn(true); @@ -232,7 +231,12 @@ void testExtractReadMeFileFromContents() throws IOException { List contents = List.of(mockReadmeFile, mockImageFile); + ReadmeContentsModel readmeContentsModel = new ReadmeContentsModel(); + readmeContentsModel.setDescription(mockReadmeContent); + when(ProductContentUtils.hasImageDirectives(anyString())).thenReturn(true); + when(ProductContentUtils.getExtractedPartsOfReadme(nullable(String.class))).thenReturn(readmeContentsModel); + when(imageService.mappingImageFromGHContent(anyString(), any())).thenReturn(getMockImage()); ProductModuleContent productModuleContent = new ProductModuleContent(); @@ -241,8 +245,6 @@ void testExtractReadMeFileFromContents() throws IOException { verify(mockReadmeFile, times(1)).read(); verify(imageService, times(1)).mappingImageFromGHContent(anyString(), eq(mockImageFile)); - mockedProductContentUtils.verify( - () -> ProductContentUtils.updateProductModuleTabContents(productModuleContent, new HashMap<>())); } } diff --git a/marketplace-service/src/test/java/com/axonivy/market/service/impl/MetadataServiceImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/service/impl/MetadataServiceImplTest.java index 07b7b7490..073652603 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/service/impl/MetadataServiceImplTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/service/impl/MetadataServiceImplTest.java @@ -5,6 +5,7 @@ import com.axonivy.market.entity.MavenArtifactVersion; import com.axonivy.market.entity.Metadata; import com.axonivy.market.entity.Product; +import com.axonivy.market.entity.ProductJsonContent; import com.axonivy.market.model.MavenArtifactModel; import com.axonivy.market.repository.MavenArtifactVersionRepository; import com.axonivy.market.repository.MetadataRepository; @@ -16,6 +17,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentMatchers; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockedStatic; @@ -29,6 +31,10 @@ import java.util.List; import java.util.Set; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + @Log4j2 @ExtendWith(MockitoExtension.class) class MetadataServiceImplTest extends BaseSetup { @@ -45,15 +51,33 @@ class MetadataServiceImplTest extends BaseSetup { @Mock private MetadataSyncRepository metadataSyncRepo; + @Test + void testUpdateArtifactAndMetaDataForProduct() { + ProductJsonContent mockProductJsonContent = getMockProductJsonContent(); + mockProductJsonContent.setProductId(MOCK_PRODUCT_ID); + + Artifact mockArtifact = getMockArtifact(); + Metadata mockMetadata = buildMocKMetadata(); + Product mockProduct = getMockProduct(); + try (MockedStatic mockUtils = Mockito.mockStatic(MavenUtils.class)) { + mockUtils.when(() -> MavenUtils.getMetadataContentFromUrl(ArgumentMatchers.anyString())).thenReturn(null); + mockUtils.when(() -> MavenUtils.convertArtifactsToMetadataSet(any(), any())).thenReturn(Set.of(mockMetadata)); + + metadataService.updateArtifactAndMetadata(mockProduct.getId(), List.of(MOCK_RELEASED_VERSION) ,List.of(mockArtifact)); + + verify(mavenArtifactVersionRepo, times(1)).save(any()); + verify(metadataRepo, times(1)).saveAll(any()); + } + } + @Test void testGetArtifactsFromNonSyncedVersion() { - Mockito.when(productJsonRepo.findByProductIdAndVersion(MOCK_PRODUCT_ID, MOCK_RELEASED_VERSION)).thenReturn( + Mockito.when(productJsonRepo.findByProductIdAndVersionIn(MOCK_PRODUCT_ID, List.of(MOCK_RELEASED_VERSION))).thenReturn( List.of(getMockProductJsonContent())); Set artifacts = metadataService.getArtifactsFromNonSyncedVersion(MOCK_PRODUCT_ID, Collections.emptyList()); Assertions.assertTrue(CollectionUtils.isEmpty(artifacts)); - Mockito.verify(productJsonRepo, Mockito.never()).findByProductIdAndVersion(Mockito.anyString(), - Mockito.anyString()); + Mockito.verify(productJsonRepo, Mockito.never()).findByProductIdAndVersionIn(Mockito.anyString(), any()); artifacts = metadataService.getArtifactsFromNonSyncedVersion(MOCK_PRODUCT_ID, List.of(MOCK_RELEASED_VERSION)); Assertions.assertEquals(2, artifacts.size()); Assertions.assertEquals("bpmn-statistic-demo", artifacts.iterator().next().getArtifactId()); diff --git a/marketplace-service/src/test/java/com/axonivy/market/service/impl/ProductContentServiceImplTest.java b/marketplace-service/src/test/java/com/axonivy/market/service/impl/ProductContentServiceImplTest.java index bd9dc623b..d008f9adf 100644 --- a/marketplace-service/src/test/java/com/axonivy/market/service/impl/ProductContentServiceImplTest.java +++ b/marketplace-service/src/test/java/com/axonivy/market/service/impl/ProductContentServiceImplTest.java @@ -5,6 +5,7 @@ import com.axonivy.market.constants.ProductJsonConstants; import com.axonivy.market.entity.ProductModuleContent; import com.axonivy.market.service.ImageService; +import com.axonivy.market.service.MetadataService; import com.axonivy.market.service.ProductJsonContentService; import com.axonivy.market.util.MavenUtils; import org.junit.jupiter.api.Test; @@ -34,6 +35,8 @@ class ProductContentServiceImplTest extends BaseSetup { private ProductJsonContentService productJsonContentService; @Mock private ImageService imageService; + @Mock + private MetadataService metadataService; @Test void testUpdateDependencyContentsFromProductJson() throws IOException { 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 7b5558424..30f87f52d 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 @@ -694,7 +694,6 @@ void testSyncOneProduct() throws IOException { when(productRepo.findById(anyString())).thenReturn(Optional.of(mockProduct)); var mockContents = mockMetaJsonAndLogoList(); when(marketRepoService.getMarketItemByPath(anyString())).thenReturn(mockContents); - when(metadataService.syncProductMetadata(any(Product.class))).thenReturn(true); when(productRepo.save(any(Product.class))).thenReturn(mockProduct); // Executes var result = productService.syncOneProduct(SAMPLE_PRODUCT_PATH, SAMPLE_PRODUCT_ID, false);