Skip to content

Commit

Permalink
feature/MARPP-759 open marketplace from within designer correct version
Browse files Browse the repository at this point in the history
  • Loading branch information
ntqdinh-axonivy authored Aug 20, 2024
1 parent 2c25696 commit 1ed484a
Show file tree
Hide file tree
Showing 34 changed files with 816 additions and 305 deletions.
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
package com.axonivy.market.assembler;

import com.axonivy.market.constants.CommonConstants;
import com.axonivy.market.constants.GitHubConstants;
import com.axonivy.market.constants.RequestMappingConstants;
import com.axonivy.market.controller.ProductDetailsController;
import com.axonivy.market.entity.Product;
import com.axonivy.market.entity.ProductModuleContent;
import com.axonivy.market.enums.NonStandardProduct;
import com.axonivy.market.model.ProductDetailModel;
import org.apache.commons.lang3.StringUtils;
import org.springframework.hateoas.server.mvc.RepresentationModelAssemblerSupport;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Optional;

import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
Expand All @@ -30,32 +27,36 @@ public ProductDetailModelAssembler(ProductModelAssembler productModelAssembler)

@Override
public ProductDetailModel toModel(Product product) {
return createModel(product, StringUtils.EMPTY);
return createModel(product, StringUtils.EMPTY, StringUtils.EMPTY);
}

public ProductDetailModel toModel(Product product, String version) {
String productId = Optional.ofNullable(product).map(Product::getId).orElse(StringUtils.EMPTY);
return createModel(product, convertVersionToTag(productId, version));
public ProductDetailModel toModel(Product product, String requestPath) {
return createModel(product, StringUtils.EMPTY, requestPath);
}

private ProductDetailModel createModel(Product product, String tag) {
if (product == null) {
return new ProductDetailModel();
}
public ProductDetailModel toModel(Product product, String version, String requestPath) {
return createModel(product, version, requestPath);
}

private ProductDetailModel createModel(Product product, String version, String requestPath) {
ResponseEntity<ProductDetailModel> selfLinkWithTag;
ProductDetailModel model = instantiateModel(product);
productModelAssembler.createResource(model, product);
if (StringUtils.isBlank(tag)) {
selfLinkWithTag = methodOn(ProductDetailsController.class).findProductDetails(product.getId());
} else {
selfLinkWithTag = methodOn(ProductDetailsController.class).findProductDetailsByVersion(product.getId(), tag);
}
String productId = Optional.of(product).map(Product::getId).orElse(StringUtils.EMPTY);
selfLinkWithTag = switch (requestPath) {
case RequestMappingConstants.BEST_MATCH_BY_ID_AND_VERSION ->
methodOn(ProductDetailsController.class).findBestMatchProductDetailsByVersion(productId, version);
case RequestMappingConstants.BY_ID_AND_VERSION ->
methodOn(ProductDetailsController.class).findProductDetailsByVersion(productId, version);
default ->
methodOn(ProductDetailsController.class).findProductDetails(productId);
};
model.add(linkTo(selfLinkWithTag).withSelfRel());
createDetailResource(model, product, tag);
createDetailResource(model, product);
return model;
}

private void createDetailResource(ProductDetailModel model, Product product, String tag) {
private void createDetailResource(ProductDetailModel model, Product product) {
model.setVendor(product.getVendor());
model.setNewestReleaseVersion(product.getNewestReleaseVersion());
model.setPlatformReview(product.getPlatformReview());
Expand All @@ -67,28 +68,6 @@ private void createDetailResource(ProductDetailModel model, Product product, Str
model.setContactUs(product.getContactUs());
model.setCost(product.getCost());
model.setInstallationCount(product.getInstallationCount());

if (StringUtils.isBlank(tag) && StringUtils.isNotBlank(product.getNewestReleaseVersion())) {
tag = product.getNewestReleaseVersion();
}
ProductModuleContent content = getProductModuleContentByTag(product.getProductModuleContents(), tag);
model.setProductModuleContent(content);
}

private ProductModuleContent getProductModuleContentByTag(List<ProductModuleContent> contents, String tag) {
return contents.stream().filter(content -> StringUtils.equals(content.getTag(), tag)).findAny().orElse(null);
}

public String convertVersionToTag(String productId, String version) {
if (StringUtils.isBlank(version)) {
return version;
}
String[] versionParts = version.split(CommonConstants.SPACE_SEPARATOR);
String versionNumber = versionParts[versionParts.length - 1];
NonStandardProduct product = NonStandardProduct.findById(productId);
if (product.isVersionTagNumberOnly()) {
return versionNumber;
}
return GitHubConstants.STANDARD_TAG_PREFIX.concat(versionNumber);
model.setProductModuleContent(CollectionUtils.firstElement(product.getProductModuleContents()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.axonivy.market.constants;

public class MongoDBConstants {
private MongoDBConstants() {
}

public static final String ID ="_id";
public static final String ADD_FIELD ="$addFields";
public static final String PRODUCT_MODULE_CONTENTS ="productModuleContents";
public static final String PRODUCT_MODULE_CONTENT ="productModuleContent";
public static final String PRODUCT_MODULE_CONTENT_QUERY ="$productModuleContents";
public static final String FILTER ="$filter";
public static final String INPUT ="input";
public static final String AS ="as";
public static final String CONDITION ="cond";
public static final String EQUAL ="$eq";
public static final String PRODUCT_MODULE_CONTENT_TAG ="$$productModuleContent.tag";
public static final String PRODUCT_COLLECTION ="Product";
public static final String NEWEST_RELEASED_VERSION_QUERY = "$newestReleaseVersion";
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ public class RequestMappingConstants {
public static final String GIT_HUB_LOGIN = "/github/login";
public static final String AUTH = "/auth";
public static final String BY_ID = "/{id}";
public static final String BY_ID_AND_TAG = "/{id}/{tag}";
public static final String BY_ID_AND_VERSION = "/{id}/{version}";
public static final String BEST_MATCH_BY_ID_AND_VERSION = "/{id}/{version}/bestmatch";
public static final String VERSIONS_BY_ID = "/{id}/versions";
public static final String PRODUCT_BY_ID = "/product/{id}";
public static final String PRODUCT_RATING_BY_ID = "/product/{id}/rating";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ public class RequestParamConstants {
public static final String RESET_SYNC = "resetSync";
public static final String SHOW_DEV_VERSION = "isShowDevVersion";
public static final String DESIGNER_VERSION = "designerVersion";
public static final String VERSION = "version";
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package com.axonivy.market.controller;

import static com.axonivy.market.constants.RequestMappingConstants.BY_ID;
import static com.axonivy.market.constants.RequestMappingConstants.BY_ID_AND_TAG;
import static com.axonivy.market.constants.RequestMappingConstants.BY_ID_AND_VERSION;
import static com.axonivy.market.constants.RequestMappingConstants.INSTALLATION_COUNT_BY_ID;
import static com.axonivy.market.constants.RequestMappingConstants.PRODUCT_DETAILS;
import static com.axonivy.market.constants.RequestMappingConstants.VERSIONS_BY_ID;
import static com.axonivy.market.constants.RequestMappingConstants.BEST_MATCH_BY_ID_AND_VERSION;
import static com.axonivy.market.constants.RequestParamConstants.DESIGNER_VERSION;
import static com.axonivy.market.constants.RequestParamConstants.ID;
import static com.axonivy.market.constants.RequestParamConstants.SHOW_DEV_VERSION;
import static com.axonivy.market.constants.RequestParamConstants.TAG;
import static com.axonivy.market.constants.RequestParamConstants.VERSION;


import java.util.List;

Expand Down Expand Up @@ -47,30 +49,39 @@ public ProductDetailsController(VersionService versionService, ProductService pr
this.detailModelAssembler = detailModelAssembler;
}

@GetMapping(BY_ID_AND_TAG)
@Operation(summary = "Find product detail by product id and release tag.", description = "get product detail by it product id and release tag")
@GetMapping(BY_ID_AND_VERSION)
@Operation(summary = "Find product detail by product id and release version.", description = "get product detail by it product id and release version")
public ResponseEntity<ProductDetailModel> findProductDetailsByVersion(
@PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "adobe-acrobat-connector", in = ParameterIn.PATH) String id,
@PathVariable(TAG) @Parameter(description = "Release tag (from git hub repo tags)", example = "v10.0.20", in = ParameterIn.PATH) String tag) {
var productDetail = productService.fetchProductDetail(id);
return new ResponseEntity<>(detailModelAssembler.toModel(productDetail, tag), HttpStatus.OK);
@PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "approval-decision-utils", in = ParameterIn.PATH) String id,
@PathVariable(VERSION) @Parameter(description = "Release version (from maven metadata.xml)", example = "10.0.20", in = ParameterIn.PATH) String version) {
var productDetail = productService.fetchProductDetailByIdAndVersion(id, version);
return new ResponseEntity<>(detailModelAssembler.toModel(productDetail, version, BY_ID_AND_VERSION), HttpStatus.OK);
}

@GetMapping(BEST_MATCH_BY_ID_AND_VERSION)
@Operation(summary = "Find best match product detail by product id and version.", description = "get product detail by it product id and version")
public ResponseEntity<ProductDetailModel> findBestMatchProductDetailsByVersion(
@PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "approval-decision-utils", in = ParameterIn.PATH) String id,
@PathVariable(VERSION) @Parameter(description = "Version", example = "10.0.20", in = ParameterIn.PATH) String version) {
var productDetail = productService.fetchBestMatchProductDetail(id,version);
return new ResponseEntity<>(detailModelAssembler.toModel(productDetail, version, BEST_MATCH_BY_ID_AND_VERSION), HttpStatus.OK);
}

@CrossOrigin(originPatterns = "*")
@PutMapping(INSTALLATION_COUNT_BY_ID)
@Operation(summary = "Update installation count of product", description = "By default, increase installation count when click download product files by users")
public ResponseEntity<Integer> syncInstallationCount(
@PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "adobe-acrobat-connector", in = ParameterIn.PATH) String productId) {
@PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "approval-decision-utils", in = ParameterIn.PATH) String productId) {
int result = productService.updateInstallationCountForProduct(productId);
return new ResponseEntity<>(result, HttpStatus.OK);
}

@GetMapping(BY_ID)
@Operation(summary = "increase installation count by 1", description = "update installation count when click download product files by users")
public ResponseEntity<ProductDetailModel> findProductDetails(
@PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "adobe-acrobat-connector", in = ParameterIn.PATH) String id) {
@PathVariable(ID) @Parameter(description = "Product id (from meta.json)", example = "approval-decision-utils", in = ParameterIn.PATH) String id) {
var productDetail = productService.fetchProductDetail(id);
return new ResponseEntity<>(detailModelAssembler.toModel(productDetail), HttpStatus.OK);
return new ResponseEntity<>(detailModelAssembler.toModel(productDetail, BY_ID), HttpStatus.OK);
}

@GetMapping(VERSIONS_BY_ID)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public class Product implements Serializable {
private List<MavenArtifact> artifacts;
private Boolean synchronizedInstallationCount;
private Integer customOrder;
private List<String> releasedVersions;

@Override
public int hashCode() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -73,6 +74,7 @@ public static Product mappingByMetaJSONFile(Product product, GHContent ghContent
product.setCompatibility(meta.getCompatibility());
extractSourceUrl(product, meta);
product.setArtifacts(meta.getMavenArtifacts());
product.setReleasedVersions(new ArrayList<>());
return product;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.axonivy.market.repository;

import com.axonivy.market.entity.Product;

import java.util.List;

public interface CustomProductRepository {
Product getProductByIdAndTag(String id, String tag);

Product getProductById(String id);

List<String> getReleasedVersionsById(String id);

int updateInitialCount(String productId, int initialCount);

int increaseInstallationCount(String productId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import com.axonivy.market.entity.Product;

@Repository
public interface ProductRepository extends MongoRepository<Product, String>, ProductSearchRepository {
public interface ProductRepository extends MongoRepository<Product, String>, ProductSearchRepository, CustomProductRepository {

Product findByLogoUrl(String logoUrl);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package com.axonivy.market.repository.impl;

import com.axonivy.market.constants.MongoDBConstants;
import com.axonivy.market.entity.Product;
import com.axonivy.market.repository.CustomProductRepository;
import org.bson.Document;
import org.springframework.data.mongodb.core.FindAndModifyOptions;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;


public class CustomProductRepositoryImpl implements CustomProductRepository {
private final MongoTemplate mongoTemplate;

public CustomProductRepositoryImpl(MongoTemplate mongoTemplate) {
this.mongoTemplate = mongoTemplate;
}

private AggregationOperation createIdMatchOperation(String id) {
return Aggregation.match(Criteria.where(MongoDBConstants.ID).is(id));
}

public Document createDocumentFilterProductModuleContentByTag(String tag) {
Document isProductModuleContentOfCurrentTag = new Document(MongoDBConstants.EQUAL,
Arrays.asList(MongoDBConstants.PRODUCT_MODULE_CONTENT_TAG, tag));
Document loopOverProductModuleContents = new Document(MongoDBConstants.INPUT,
MongoDBConstants.PRODUCT_MODULE_CONTENT_QUERY)
.append(MongoDBConstants.AS, MongoDBConstants.PRODUCT_MODULE_CONTENT);
return loopOverProductModuleContents.append(MongoDBConstants.CONDITION, isProductModuleContentOfCurrentTag);
}

private AggregationOperation createReturnFirstModuleContentOperation() {
return context -> new Document(MongoDBConstants.ADD_FIELD,
new Document(MongoDBConstants.PRODUCT_MODULE_CONTENTS,
new Document(MongoDBConstants.FILTER, createDocumentFilterProductModuleContentByTag(MongoDBConstants.NEWEST_RELEASED_VERSION_QUERY))));
}

private AggregationOperation createReturnFirstMatchTagModuleContentOperation(String tag) {
return context -> new Document(MongoDBConstants.ADD_FIELD,
new Document(MongoDBConstants.PRODUCT_MODULE_CONTENTS,
new Document(MongoDBConstants.FILTER, createDocumentFilterProductModuleContentByTag(tag))));
}

public Product queryProductByAggregation(Aggregation aggregation) {
return Optional.of(mongoTemplate.aggregate(aggregation, MongoDBConstants.PRODUCT_COLLECTION, Product.class))
.map(AggregationResults::getUniqueMappedResult).orElse(null);
}

@Override
public Product getProductByIdAndTag(String id, String tag) {
// Create the aggregation pipeline
Aggregation aggregation = Aggregation.newAggregation(createIdMatchOperation(id), createReturnFirstMatchTagModuleContentOperation(tag));
return queryProductByAggregation(aggregation);
}

@Override
public Product getProductById(String id) {
Aggregation aggregation = Aggregation.newAggregation(createIdMatchOperation(id), createReturnFirstModuleContentOperation());
return queryProductByAggregation(aggregation);
}

@Override
public List<String> getReleasedVersionsById(String id) {
Aggregation aggregation = Aggregation.newAggregation(createIdMatchOperation(id));
Product product = queryProductByAggregation(aggregation);
if (Objects.isNull(product)) {
return Collections.emptyList();
}
return product.getReleasedVersions();

}

public int updateInitialCount(String productId, int initialCount) {
Update update = new Update().inc("InstallationCount", initialCount).set("SynchronizedInstallationCount", true);
mongoTemplate.updateFirst(createQueryById(productId), update, Product.class);
return Optional.ofNullable(getProductById(productId)).map(Product::getInstallationCount).orElse(0);
}

@Override
public int increaseInstallationCount(String productId) {
Update update = new Update().inc("InstallationCount", 1);
// Find and modify the document, then return the updated InstallationCount field
Product updatedProduct = mongoTemplate.findAndModify(createQueryById(productId), update,
FindAndModifyOptions.options().returnNew(true), Product.class);
return updatedProduct != null ? updatedProduct.getInstallationCount() : 0;
}

private Query createQueryById(String id) {
return new Query(Criteria.where(MongoDBConstants.ID).is(id));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public ProductSearchRepositoryImpl(MongoTemplate mongoTemplate) {
this.mongoTemplate = mongoTemplate;
}


@Override
public Page<Product> searchByCriteria(ProductSearchCriteria searchCriteria, Pageable pageable) {
return getResultAsPageable(pageable, buildCriteriaSearch(searchCriteria));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,9 @@ public interface ProductService {
void clearAllProducts();

void addCustomSortProduct(ProductCustomSortRequest customSort) throws InvalidParamException;

Product fetchBestMatchProductDetail(String id, String version);

Product fetchProductDetailByIdAndVersion(String id, String version);

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@

public interface VersionService {

List<String> getVersionsToDisplay(Boolean isShowDevVersion, String designerVersion);

List<String> getVersionsFromArtifactDetails(String repoUrl, String groupId, String artifactId);

String buildMavenMetadataUrlFromArtifact(String repoUrl, String groupId, String artifactId);
Expand Down
Loading

0 comments on commit 1ed484a

Please sign in to comment.