diff --git a/package-lock.json b/package-lock.json index c757228b2cbc7f7d9d5280848c8c5b1bf2156425..4f806e283d0c8e34f7e5a3a888f5cb88dcc1f993 100644 --- a/package-lock.json +++ b/package-lock.json @@ -181,6 +181,11 @@ "ansi-regex": "^4.1.0" } }, + "uuid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", + "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" + }, "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", diff --git a/package.json b/package.json index 40bd4ded330c2f4bdfdae0ae532addec32eb2579..5993bd71b2e9c9111a67b8bc2620f284651802ae 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "dependencies": { "axios": "^0.19.0", "random-words": "^1.1.0", + "uuid": "^3.3.3", "yargs": "^14.0.0" } } diff --git a/pom.xml b/pom.xml index 5bb4f6219ffff0c1d535b912f7e3e218c3f68779..25df236c7cd3e9811920db4084528af0f9704c23 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>org.eclipsefoundation</groupId> <artifactId>marketplace-rest-api</artifactId> - <version>0.1-ALPHA</version> + <version>0.2-BETA</version> <properties> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <surefire-plugin.version>2.22.0</surefire-plugin.version> diff --git a/src/main/java/org/eclipsefoundation/marketplace/dao/impl/DefaultMongoDao.java b/src/main/java/org/eclipsefoundation/marketplace/dao/impl/DefaultMongoDao.java index 586d631d258233c52382f7844610e705062f9ed5..56829ef4ae15ba3259d282dc88ae9a037e837d00 100644 --- a/src/main/java/org/eclipsefoundation/marketplace/dao/impl/DefaultMongoDao.java +++ b/src/main/java/org/eclipsefoundation/marketplace/dao/impl/DefaultMongoDao.java @@ -60,8 +60,6 @@ public class DefaultMongoDao implements MongoDao { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Querying MongoDB using the following query: {}", q); } - - LOGGER.error("{}", q); LOGGER.debug("Getting aggregate results"); return getCollection(q.getDocType()).aggregate(q.getPipeline(getLimit(q)), q.getDocType()).limit(getLimit(q)) diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/Category.java b/src/main/java/org/eclipsefoundation/marketplace/dto/Category.java index 29d30607c16d7b6d3b7f5f03f72e6bd4a2177d11..1d9dbb4bdf83d10313555286ad6beb8c3cc494fa 100644 --- a/src/main/java/org/eclipsefoundation/marketplace/dto/Category.java +++ b/src/main/java/org/eclipsefoundation/marketplace/dto/Category.java @@ -9,8 +9,7 @@ */ package org.eclipsefoundation.marketplace.dto; -import java.util.ArrayList; -import java.util.List; +import java.util.Objects; import io.quarkus.runtime.annotations.RegisterForReflection; @@ -22,22 +21,21 @@ import io.quarkus.runtime.annotations.RegisterForReflection; */ @RegisterForReflection public class Category { - private Integer id; + private String id; private String name; private String url; - private List<Integer> marketIds; /** * @return the id */ - public int getId() { + public String getId() { return id; } /** * @param id the id to set */ - public void setId(int id) { + public void setId(String id) { this.id = id; } @@ -69,58 +67,24 @@ public class Category { this.url = url; } - /** - * @return the marketIds - */ - public List<Integer> getMarketIds() { - return new ArrayList<>(marketIds); - } - - /** - * @param marketIds the marketIds to set - */ - public void setMarketIds(List<Integer> marketIds) { - this.marketIds = new ArrayList<>(marketIds); - } - @Override public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + id; - result = prime * result + ((marketIds == null) ? 0 : marketIds.hashCode()); - result = prime * result + ((name == null) ? 0 : name.hashCode()); - result = prime * result + ((url == null) ? 0 : url.hashCode()); - return result; + return Objects.hash(id, name, url); } @Override public boolean equals(Object obj) { - if (this == obj) + if (this == obj) { return true; - if (obj == null) + } + if (obj == null) { return false; - if (getClass() != obj.getClass()) + } + if (getClass() != obj.getClass()) { return false; + } Category other = (Category) obj; - if (id != other.id) - return false; - if (marketIds == null) { - if (other.marketIds != null) - return false; - } else if (!marketIds.equals(other.marketIds)) - return false; - if (name == null) { - if (other.name != null) - return false; - } else if (!name.equals(other.name)) - return false; - if (url == null) { - if (other.url != null) - return false; - } else if (!url.equals(other.url)) - return false; - return true; + return Objects.equals(id, other.id) && Objects.equals(name, other.name) && Objects.equals(url, other.url); } @Override @@ -132,8 +96,6 @@ public class Category { builder.append(name); builder.append(", url="); builder.append(url); - builder.append(", marketIds="); - builder.append(marketIds); builder.append("]"); return builder.toString(); } diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/Install.java b/src/main/java/org/eclipsefoundation/marketplace/dto/Install.java index 69a2be27d43275f458a35e6f902420a54fe5909e..09d3138392ecb8f1a202d5287ce16298bd4430b4 100644 --- a/src/main/java/org/eclipsefoundation/marketplace/dto/Install.java +++ b/src/main/java/org/eclipsefoundation/marketplace/dto/Install.java @@ -8,6 +8,9 @@ package org.eclipsefoundation.marketplace.dto; import java.sql.Date; +import org.eclipsefoundation.marketplace.model.RequestWrapper; +import org.eclipsefoundation.marketplace.namespace.UrlParameterNames; + /** * Domain object representing the data stored for installs. * @@ -18,9 +21,17 @@ public class Install { private Date installDate; private String os; private String version; - private int listingId; + private String listingId; private String javaVersion; + public static Install createFromRequest(RequestWrapper wrap) { + Install install = new Install(); + install.installDate = new Date(System.currentTimeMillis()); + install.listingId = wrap.getFirstParam(UrlParameterNames.ID).get(); + + return install; + } + /** * @return the installDate */ @@ -66,14 +77,14 @@ public class Install { /** * @return the listingId */ - public int getListingId() { + public String getListingId() { return listingId; } /** * @param listingId the listingId to set */ - public void setListingId(int listingId) { + public void setListingId(String listingId) { this.listingId = listingId; } diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/Listing.java b/src/main/java/org/eclipsefoundation/marketplace/dto/Listing.java index f12e7ec131bbfc765e8534de59641666c7aa0843..c4819e02cc86a35b7be6fd16a2741cb19f543036 100644 --- a/src/main/java/org/eclipsefoundation/marketplace/dto/Listing.java +++ b/src/main/java/org/eclipsefoundation/marketplace/dto/Listing.java @@ -17,7 +17,7 @@ import javax.json.bind.annotation.JsonbProperty; import javax.json.bind.annotation.JsonbTransient; import org.eclipsefoundation.marketplace.model.SortableField; -import org.eclipsefoundation.marketplace.namespace.MongoFieldNames; +import org.eclipsefoundation.marketplace.namespace.DatabaseFieldNames; import io.quarkus.runtime.annotations.RegisterForReflection; @@ -29,10 +29,7 @@ import io.quarkus.runtime.annotations.RegisterForReflection; @RegisterForReflection public class Listing { - @JsonbTransient private String id; - @SortableField(name = "listing_id") - private long listingId; @SortableField private String title; private String url; @@ -55,16 +52,16 @@ public class Listing { @SortableField private long favoriteCount; - @SortableField(name = MongoFieldNames.CREATION_DATE) - @JsonbProperty(MongoFieldNames.CREATION_DATE) + @SortableField(name = DatabaseFieldNames.CREATION_DATE) + @JsonbProperty(DatabaseFieldNames.CREATION_DATE) private long creationDate; - @SortableField(name = MongoFieldNames.UPDATE_DATE) - @JsonbProperty(MongoFieldNames.UPDATE_DATE) + @SortableField(name = DatabaseFieldNames.UPDATE_DATE) + @JsonbProperty(DatabaseFieldNames.UPDATE_DATE) private long updateDate; - @JsonbProperty(MongoFieldNames.LICENSE_TYPE) + @JsonbProperty(DatabaseFieldNames.LICENSE_TYPE) private String license; - private List<Integer> categoryIds; + private List<String> categoryIds; private List<Category> categories; private List<Organization> organizations; private List<Author> authors; @@ -96,21 +93,7 @@ public class Listing { public void setId(String id) { this.id = id; } - - /** - * @return the listingId - */ - public long getListingId() { - return listingId; - } - - /** - * @param listingId the id to set - */ - public void setListingId(long listingId) { - this.listingId = listingId; - } - + /** * @return the title */ @@ -325,14 +308,15 @@ public class Listing { /** * @return the categoryIds */ - public List<Integer> getCategoryIds() { + @JsonbTransient + public List<String> getCategoryIds() { return categoryIds; } /** * @param categoryIds the categoryIds to set */ - public void setCategoryIds(List<Integer> categoryIds) { + public void setCategoryIds(List<String> categoryIds) { this.categoryIds = new ArrayList<>(categoryIds); } @@ -411,11 +395,42 @@ public class Listing { this.versions = new ArrayList<>(versions); } + @Override + public int hashCode() { + return Objects.hash(authors, body, categories, categoryIds, creationDate, favoriteCount, foundationMember, + homepageUrl, id, installsRecent, installsTotal, license, logo, organizations, status, supportUrl, tags, + teaser, title, updateDate, url, versions); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Listing other = (Listing) obj; + return Objects.equals(authors, other.authors) && Objects.equals(body, other.body) + && Objects.equals(categories, other.categories) && Objects.equals(categoryIds, other.categoryIds) + && creationDate == other.creationDate && favoriteCount == other.favoriteCount + && foundationMember == other.foundationMember && Objects.equals(homepageUrl, other.homepageUrl) + && Objects.equals(id, other.id) && installsRecent == other.installsRecent + && installsTotal == other.installsTotal && Objects.equals(license, other.license) + && Objects.equals(logo, other.logo) && Objects.equals(organizations, other.organizations) + && Objects.equals(status, other.status) && Objects.equals(supportUrl, other.supportUrl) + && Objects.equals(tags, other.tags) && Objects.equals(teaser, other.teaser) + && Objects.equals(title, other.title) && updateDate == other.updateDate + && Objects.equals(url, other.url) && Objects.equals(versions, other.versions); + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(", id=").append(id); - sb.append(", listingId=").append(listingId); sb.append(", title=").append(title); sb.append(", url=").append(url); sb.append(", supportUrl=").append(supportUrl); diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/Market.java b/src/main/java/org/eclipsefoundation/marketplace/dto/Market.java index 0a41208468257f2588a45aa867bdb0a149331333..2b1c2e0b8a329ecbde4278ad6481f51a79ac4788 100644 --- a/src/main/java/org/eclipsefoundation/marketplace/dto/Market.java +++ b/src/main/java/org/eclipsefoundation/marketplace/dto/Market.java @@ -9,11 +9,11 @@ */ package org.eclipsefoundation.marketplace.dto; -import java.security.InvalidParameterException; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; -import java.util.Objects; + +import javax.json.bind.annotation.JsonbTransient; import io.quarkus.runtime.annotations.RegisterForReflection; @@ -25,9 +25,10 @@ import io.quarkus.runtime.annotations.RegisterForReflection; */ @RegisterForReflection public class Market { - private int id; + private String id; private String name; private String url; + private List<String> categoryIds; private List<Category> categories; /** @@ -36,19 +37,20 @@ public class Market { */ public Market() { this.categories = new LinkedList<>(); + this.categoryIds = new LinkedList<>(); } /** * @return the id */ - public int getId() { + public String getId() { return id; } /** * @param id the id to set */ - public void setId(int id) { + public void setId(String id) { this.id = id; } @@ -90,36 +92,23 @@ public class Market { /** * @param categories the categories to set */ + @JsonbTransient public void setCategories(List<Category> categories) { this.categories = new ArrayList<>(categories); } /** - * Remove a category from the market definition. - * - * @param c the category to remove if present - * @return the category removed if it was present, or null if it was missing. - * @throws InvalidParameterException if the passed category is an illegal value - * (i.e. null) + * @return the categoryIds */ - public Category removeCategory(Category c) { - int idx = categories.indexOf(c); - if (idx > 0) { - return this.categories.remove(idx); - } else { - return null; - } + @JsonbTransient + public List<String> getCategoryIds() { + return new ArrayList<>(categoryIds); } /** - * Add a category to the market definition. - * - * @param c the category to add - * @throws IllegalArgumentException if the passed category is an illegal value - * (i.e. null) + * @param categoryIds the categoryIds to set */ - public void addCategory(Category c) { - Objects.requireNonNull(c); - this.categories.add(c); + public void setCategoryIds(List<String> categoryIds) { + this.categoryIds = new ArrayList<>(categoryIds); } } diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/CatalogCodec.java b/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/CatalogCodec.java index a08d4ad7289da51c2ff59c6b70b6a7a80a2d47e0..cbaa4dc0f091fb0c35720d72c29761a1f791a3b3 100644 --- a/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/CatalogCodec.java +++ b/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/CatalogCodec.java @@ -6,6 +6,7 @@ */ package org.eclipsefoundation.marketplace.dto.codecs; +import java.util.UUID; import java.util.stream.Collectors; import org.bson.BsonReader; @@ -19,7 +20,7 @@ import org.bson.codecs.DecoderContext; import org.bson.codecs.EncoderContext; import org.eclipsefoundation.marketplace.dto.Catalog; import org.eclipsefoundation.marketplace.dto.converters.TabConverter; -import org.eclipsefoundation.marketplace.namespace.MongoFieldNames; +import org.eclipsefoundation.marketplace.namespace.DatabaseFieldNames; import com.mongodb.MongoClient; @@ -49,14 +50,14 @@ public class CatalogCodec implements CollectibleCodec<Catalog> { public void encode(BsonWriter writer, Catalog value, EncoderContext encoderContext) { Document doc = new Document(); - doc.put(MongoFieldNames.DOCID, value.getId()); - doc.put(MongoFieldNames.CATALOG_TITLE, value.getTitle()); - doc.put(MongoFieldNames.CATALOG_URL, value.getUrl()); - doc.put(MongoFieldNames.CATALOG_ICON, value.getIcon()); - doc.put(MongoFieldNames.CATALOG_SELF_CONTAINED, value.isSelfContained()); - doc.put(MongoFieldNames.CATALOG_SEARCH_ENABLED, value.isSearchEnabled()); - doc.put(MongoFieldNames.CATALOG_DEPENDENCIES_REPOSITORY, value.getDependenciesRepository()); - doc.put(MongoFieldNames.CATALOG_TABS, + doc.put(DatabaseFieldNames.DOCID, value.getId()); + doc.put(DatabaseFieldNames.CATALOG_TITLE, value.getTitle()); + doc.put(DatabaseFieldNames.CATALOG_URL, value.getUrl()); + doc.put(DatabaseFieldNames.CATALOG_ICON, value.getIcon()); + doc.put(DatabaseFieldNames.CATALOG_SELF_CONTAINED, value.isSelfContained()); + doc.put(DatabaseFieldNames.CATALOG_SEARCH_ENABLED, value.isSearchEnabled()); + doc.put(DatabaseFieldNames.CATALOG_DEPENDENCIES_REPOSITORY, value.getDependenciesRepository()); + doc.put(DatabaseFieldNames.CATALOG_TABS, value.getTabs().stream().map(tabConverter::convert).collect(Collectors.toList())); documentCodec.encode(writer, doc, encoderContext); } @@ -70,20 +71,23 @@ public class CatalogCodec implements CollectibleCodec<Catalog> { public Catalog decode(BsonReader reader, DecoderContext decoderContext) { Document document = documentCodec.decode(reader, decoderContext); Catalog out = new Catalog(); - out.setId(document.getString(MongoFieldNames.DOCID)); - out.setUrl(document.getString(MongoFieldNames.CATALOG_URL)); - out.setTitle(document.getString(MongoFieldNames.CATALOG_TITLE)); - out.setIcon(document.getString(MongoFieldNames.CATALOG_ICON)); - out.setSelfContained(document.getBoolean(MongoFieldNames.CATALOG_SELF_CONTAINED)); - out.setSearchEnabled(document.getBoolean(MongoFieldNames.CATALOG_SEARCH_ENABLED)); - out.setDependenciesRepository(document.getString(MongoFieldNames.CATALOG_DEPENDENCIES_REPOSITORY)); - out.setTabs(document.getList(MongoFieldNames.CATALOG_TABS, Document.class).stream().map(tabConverter::convert) + out.setId(document.getString(DatabaseFieldNames.DOCID)); + out.setUrl(document.getString(DatabaseFieldNames.CATALOG_URL)); + out.setTitle(document.getString(DatabaseFieldNames.CATALOG_TITLE)); + out.setIcon(document.getString(DatabaseFieldNames.CATALOG_ICON)); + out.setSelfContained(document.getBoolean(DatabaseFieldNames.CATALOG_SELF_CONTAINED)); + out.setSearchEnabled(document.getBoolean(DatabaseFieldNames.CATALOG_SEARCH_ENABLED)); + out.setDependenciesRepository(document.getString(DatabaseFieldNames.CATALOG_DEPENDENCIES_REPOSITORY)); + out.setTabs(document.getList(DatabaseFieldNames.CATALOG_TABS, Document.class).stream().map(tabConverter::convert) .collect(Collectors.toList())); return out; } @Override public Catalog generateIdIfAbsentFromDocument(Catalog document) { + if (!documentHasId(document)) { + document.setId(UUID.randomUUID().toString()); + } return document; } @@ -94,7 +98,6 @@ public class CatalogCodec implements CollectibleCodec<Catalog> { @Override public BsonValue getDocumentId(Catalog document) { - // TODO Auto-generated method stub return new BsonString(document.getId()); } diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/CategoryCodec.java b/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/CategoryCodec.java index 187cd7d9455101a5536b0f551308020f894c7771..944aedf4e73b32b0b894429f9a4f34b054e6a01b 100644 --- a/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/CategoryCodec.java +++ b/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/CategoryCodec.java @@ -6,8 +6,11 @@ */ package org.eclipsefoundation.marketplace.dto.codecs; -import org.bson.BsonInt32; +import java.util.UUID; + +import org.apache.commons.lang3.StringUtils; import org.bson.BsonReader; +import org.bson.BsonString; import org.bson.BsonValue; import org.bson.BsonWriter; import org.bson.Document; @@ -55,18 +58,20 @@ public class CategoryCodec implements CollectibleCodec<Category> { @Override public Category generateIdIfAbsentFromDocument(Category document) { - // TODO Auto-generated method stub + if (!documentHasId(document)) { + document.setId(UUID.randomUUID().toString()); + } return document; } @Override public boolean documentHasId(Category document) { - return document.getId() > 0; + return !StringUtils.isBlank(document.getId()); } @Override public BsonValue getDocumentId(Category document) { - return new BsonInt32(document.getId()); + return new BsonString(document.getId()); } } diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/ListingCodec.java b/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/ListingCodec.java index 8327390711f154bae6867fcac2ccc61010f7458d..993e0d00b0919cc9357fac81f5229a8536c45ba0 100644 --- a/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/ListingCodec.java +++ b/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/ListingCodec.java @@ -25,7 +25,7 @@ import org.eclipsefoundation.marketplace.dto.converters.CategoryConverter; import org.eclipsefoundation.marketplace.dto.converters.OrganizationConverter; import org.eclipsefoundation.marketplace.dto.converters.SolutionVersionConverter; import org.eclipsefoundation.marketplace.dto.converters.TagConverter; -import org.eclipsefoundation.marketplace.namespace.MongoFieldNames; +import org.eclipsefoundation.marketplace.namespace.DatabaseFieldNames; import com.mongodb.MongoClient; @@ -64,33 +64,32 @@ public class ListingCodec implements CollectibleCodec<Listing> { Document doc = new Document(); // for each of the fields, get the value from the unencoded object and set it - doc.put(MongoFieldNames.DOCID, value.getId()); - doc.put(MongoFieldNames.LISTING_ID, value.getListingId()); - doc.put(MongoFieldNames.LISTING_TITLE, value.getTitle()); - doc.put(MongoFieldNames.LISTING_URL, value.getUrl()); - doc.put(MongoFieldNames.SUPPORT_PAGE_URL, value.getSupportUrl()); - doc.put(MongoFieldNames.HOME_PAGE_URL, value.getHomepageUrl()); - doc.put(MongoFieldNames.LISTING_BODY, value.getTeaser()); - doc.put(MongoFieldNames.LISTING_TEASER, value.getBody()); - doc.put(MongoFieldNames.MARKETPLACE_FAVORITES, value.getFavoriteCount()); - doc.put(MongoFieldNames.RECENT_NSTALLS, value.getInstallsRecent()); - doc.put(MongoFieldNames.TOTAL_NSTALLS, value.getInstallsTotal()); - doc.put(MongoFieldNames.LICENSE_TYPE, value.getLicense()); - doc.put(MongoFieldNames.LISTING_STATUS, value.getStatus()); - doc.put(MongoFieldNames.UPDATE_DATE, new Date(value.getUpdateDate())); - doc.put(MongoFieldNames.CREATION_DATE, new Date(value.getCreationDate())); - doc.put(MongoFieldNames.FOUNDATION_MEMBER_FLAG, value.isFoundationMember()); - doc.put(MongoFieldNames.CATEGORY_IDS, value.getCategoryIds()); + doc.put(DatabaseFieldNames.DOCID, value.getId()); + doc.put(DatabaseFieldNames.LISTING_TITLE, value.getTitle()); + doc.put(DatabaseFieldNames.URL, value.getUrl()); + doc.put(DatabaseFieldNames.SUPPORT_PAGE_URL, value.getSupportUrl()); + doc.put(DatabaseFieldNames.HOME_PAGE_URL, value.getHomepageUrl()); + doc.put(DatabaseFieldNames.LISTING_BODY, value.getTeaser()); + doc.put(DatabaseFieldNames.LISTING_TEASER, value.getBody()); + doc.put(DatabaseFieldNames.MARKETPLACE_FAVORITES, value.getFavoriteCount()); + doc.put(DatabaseFieldNames.RECENT_NSTALLS, value.getInstallsRecent()); + doc.put(DatabaseFieldNames.TOTAL_NSTALLS, value.getInstallsTotal()); + doc.put(DatabaseFieldNames.LICENSE_TYPE, value.getLicense()); + doc.put(DatabaseFieldNames.LISTING_STATUS, value.getStatus()); + doc.put(DatabaseFieldNames.UPDATE_DATE, new Date(value.getUpdateDate())); + doc.put(DatabaseFieldNames.CREATION_DATE, new Date(value.getCreationDate())); + doc.put(DatabaseFieldNames.FOUNDATION_MEMBER_FLAG, value.isFoundationMember()); + doc.put(DatabaseFieldNames.CATEGORY_IDS, value.getCategoryIds()); // for nested document types, use the converters to safely transform into BSON // documents - doc.put(MongoFieldNames.LISTING_ORGANIZATIONS, + doc.put(DatabaseFieldNames.LISTING_ORGANIZATIONS, value.getOrganizations().stream().map(organizationConverter::convert).collect(Collectors.toList())); - doc.put(MongoFieldNames.LISTING_AUTHORS, + doc.put(DatabaseFieldNames.LISTING_AUTHORS, value.getAuthors().stream().map(authorConverter::convert).collect(Collectors.toList())); - doc.put(MongoFieldNames.LISTING_TAGS, + doc.put(DatabaseFieldNames.LISTING_TAGS, value.getTags().stream().map(tagConverter::convert).collect(Collectors.toList())); - doc.put(MongoFieldNames.LISTING_VERSIONS, + doc.put(DatabaseFieldNames.LISTING_VERSIONS, value.getVersions().stream().map(versionConverter::convert).collect(Collectors.toList())); documentCodec.encode(writer, doc, encoderContext); } @@ -106,37 +105,36 @@ public class ListingCodec implements CollectibleCodec<Listing> { Listing out = new Listing(); // for each field, get the value from the encoded object and set it in POJO - out.setId(document.getString(MongoFieldNames.DOCID)); - out.setListingId(document.getLong(MongoFieldNames.LISTING_ID)); - out.setTitle(document.getString(MongoFieldNames.LISTING_TITLE)); - out.setUrl(document.getString(MongoFieldNames.LISTING_URL)); - out.setSupportUrl(document.getString(MongoFieldNames.SUPPORT_PAGE_URL)); - out.setHomepageUrl(document.getString(MongoFieldNames.HOME_PAGE_URL)); - out.setTeaser(document.getString(MongoFieldNames.LISTING_TEASER)); - out.setBody(document.getString(MongoFieldNames.LISTING_BODY)); - out.setStatus(document.getString(MongoFieldNames.LISTING_STATUS)); - out.setInstallsRecent(document.getLong(MongoFieldNames.RECENT_NSTALLS)); - out.setInstallsTotal(document.getLong(MongoFieldNames.TOTAL_NSTALLS)); - out.setLicense(document.getString(MongoFieldNames.LICENSE_TYPE)); - out.setFavoriteCount(document.getLong(MongoFieldNames.MARKETPLACE_FAVORITES)); - out.setFoundationMember(document.getBoolean(MongoFieldNames.FOUNDATION_MEMBER_FLAG)); - out.setCategoryIds(document.getList(MongoFieldNames.CATEGORY_IDS, Integer.class)); + out.setId(document.getString(DatabaseFieldNames.DOCID)); + out.setTitle(document.getString(DatabaseFieldNames.LISTING_TITLE)); + out.setUrl(document.getString(DatabaseFieldNames.URL)); + out.setSupportUrl(document.getString(DatabaseFieldNames.SUPPORT_PAGE_URL)); + out.setHomepageUrl(document.getString(DatabaseFieldNames.HOME_PAGE_URL)); + out.setTeaser(document.getString(DatabaseFieldNames.LISTING_TEASER)); + out.setBody(document.getString(DatabaseFieldNames.LISTING_BODY)); + out.setStatus(document.getString(DatabaseFieldNames.LISTING_STATUS)); + out.setInstallsRecent(document.getLong(DatabaseFieldNames.RECENT_NSTALLS)); + out.setInstallsTotal(document.getLong(DatabaseFieldNames.TOTAL_NSTALLS)); + out.setLicense(document.getString(DatabaseFieldNames.LICENSE_TYPE)); + out.setFavoriteCount(document.getLong(DatabaseFieldNames.MARKETPLACE_FAVORITES)); + out.setFoundationMember(document.getBoolean(DatabaseFieldNames.FOUNDATION_MEMBER_FLAG)); + out.setCategoryIds(document.getList(DatabaseFieldNames.CATEGORY_IDS, String.class)); // for nested document types, use the converters to safely transform into POJO - out.setAuthors(document.getList(MongoFieldNames.LISTING_AUTHORS, Document.class).stream() + out.setAuthors(document.getList(DatabaseFieldNames.LISTING_AUTHORS, Document.class).stream() .map(authorConverter::convert).collect(Collectors.toList())); - out.setOrganizations(document.getList(MongoFieldNames.LISTING_ORGANIZATIONS, Document.class).stream() + out.setOrganizations(document.getList(DatabaseFieldNames.LISTING_ORGANIZATIONS, Document.class).stream() .map(organizationConverter::convert).collect(Collectors.toList())); - out.setTags(document.getList(MongoFieldNames.LISTING_TAGS, Document.class).stream().map(tagConverter::convert) + out.setTags(document.getList(DatabaseFieldNames.LISTING_TAGS, Document.class).stream().map(tagConverter::convert) .collect(Collectors.toList())); - out.setVersions(document.getList(MongoFieldNames.LISTING_VERSIONS, Document.class).stream() + out.setVersions(document.getList(DatabaseFieldNames.LISTING_VERSIONS, Document.class).stream() .map(versionConverter::convert).collect(Collectors.toList())); - out.setCategories(document.getList(MongoFieldNames.LISTING_CATEGORIES, Document.class).stream() + out.setCategories(document.getList(DatabaseFieldNames.LISTING_CATEGORIES, Document.class).stream() .map(categoryConverter::convert).collect(Collectors.toList())); // convert date to epoch milli - out.setCreationDate(document.getDate(MongoFieldNames.CREATION_DATE).toInstant().toEpochMilli()); - out.setUpdateDate(document.getDate(MongoFieldNames.UPDATE_DATE).toInstant().toEpochMilli()); + out.setCreationDate(document.getDate(DatabaseFieldNames.CREATION_DATE).toInstant().toEpochMilli()); + out.setUpdateDate(document.getDate(DatabaseFieldNames.UPDATE_DATE).toInstant().toEpochMilli()); return out; } diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/MarketCodec.java b/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/MarketCodec.java new file mode 100644 index 0000000000000000000000000000000000000000..52db3a960da6c0874543e3aa5f3cff4d32405086 --- /dev/null +++ b/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/MarketCodec.java @@ -0,0 +1,90 @@ +/* Copyright (c) 2019 Eclipse Foundation and others. + * This program and the accompanying materials are made available + * under the terms of the Eclipse Public License 2.0 + * which is available at http://www.eclipse.org/legal/epl-v20.html, + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipsefoundation.marketplace.dto.codecs; + +import java.util.UUID; + +import org.apache.commons.lang3.StringUtils; +import org.bson.BsonReader; +import org.bson.BsonString; +import org.bson.BsonValue; +import org.bson.BsonWriter; +import org.bson.Document; +import org.bson.codecs.Codec; +import org.bson.codecs.CollectibleCodec; +import org.bson.codecs.DecoderContext; +import org.bson.codecs.EncoderContext; +import org.eclipsefoundation.marketplace.dto.Market; +import org.eclipsefoundation.marketplace.namespace.DatabaseFieldNames; + +import com.mongodb.MongoClient; + +/** + * MongoDB codec for transcoding of {@link Market} and {@link Document} + * objects. Used when writing or retrieving objects of given type from the + * database. + * + * @author Martin Lowe + */ +public class MarketCodec implements CollectibleCodec<Market> { + private final Codec<Document> documentCodec; + + /** + * Creates the codec and initializes the codecs and converters needed to create + * a listing from end to end. + */ + public MarketCodec() { + this.documentCodec = MongoClient.getDefaultCodecRegistry().get(Document.class); + } + + @Override + public void encode(BsonWriter writer, Market value, EncoderContext encoderContext) { + Document doc = new Document(); + + doc.put(DatabaseFieldNames.DOCID, value.getId()); + doc.put(DatabaseFieldNames.URL, value.getUrl()); + doc.put(DatabaseFieldNames.MARKET_NAME, value.getName()); + doc.put(DatabaseFieldNames.CATEGORY_IDS, value.getCategoryIds()); + + documentCodec.encode(writer, doc, encoderContext); + } + + @Override + public Class<Market> getEncoderClass() { + return Market.class; + } + + @Override + public Market decode(BsonReader reader, DecoderContext decoderContext) { + Document document = documentCodec.decode(reader, decoderContext); + Market out = new Market(); + out.setId(document.getString(DatabaseFieldNames.DOCID)); + out.setUrl(document.getString(DatabaseFieldNames.URL)); + out.setName(document.getString(DatabaseFieldNames.MARKET_NAME)); + + return out; + } + + @Override + public Market generateIdIfAbsentFromDocument(Market document) { + if (!documentHasId(document)) { + document.setId(UUID.randomUUID().toString()); + } + return document; + } + + @Override + public boolean documentHasId(Market document) { + return StringUtils.isNotBlank(document.getId()); + } + + @Override + public BsonValue getDocumentId(Market document) { + return new BsonString(document.getId()); + } + +} diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/converters/CategoryConverter.java b/src/main/java/org/eclipsefoundation/marketplace/dto/converters/CategoryConverter.java index 74b55bfc9a001479a094c861da1e255b569b6831..c307fcc9d4232887ee20e6e22b4d615e75e27c8e 100644 --- a/src/main/java/org/eclipsefoundation/marketplace/dto/converters/CategoryConverter.java +++ b/src/main/java/org/eclipsefoundation/marketplace/dto/converters/CategoryConverter.java @@ -8,7 +8,7 @@ package org.eclipsefoundation.marketplace.dto.converters; import org.bson.Document; import org.eclipsefoundation.marketplace.dto.Category; -import org.eclipsefoundation.marketplace.namespace.MongoFieldNames; +import org.eclipsefoundation.marketplace.namespace.DatabaseFieldNames; /** * @author martin @@ -19,20 +19,18 @@ public class CategoryConverter implements Converter<Category> { @Override public Category convert(Document src) { Category out = new Category(); - out.setId(src.getInteger(MongoFieldNames.DOCID)); - out.setName(src.getString(MongoFieldNames.CATEGORY_NAME)); - out.setUrl(src.getString(MongoFieldNames.CATEGORY_URL)); - out.setMarketIds(src.getList(MongoFieldNames.MARKET_IDS, Integer.class)); + out.setId(src.getString(DatabaseFieldNames.DOCID)); + out.setName(src.getString(DatabaseFieldNames.CATEGORY_NAME)); + out.setUrl(src.getString(DatabaseFieldNames.CATEGORY_URL)); return out; } @Override public Document convert(Category src) { Document doc = new Document(); - doc.put(MongoFieldNames.DOCID, src.getId()); - doc.put(MongoFieldNames.CATEGORY_NAME, src.getName()); - doc.put(MongoFieldNames.CATEGORY_URL, src.getUrl()); - doc.put(MongoFieldNames.MARKET_IDS, src.getMarketIds()); + doc.put(DatabaseFieldNames.DOCID, src.getId()); + doc.put(DatabaseFieldNames.CATEGORY_NAME, src.getName()); + doc.put(DatabaseFieldNames.CATEGORY_URL, src.getUrl()); return doc; } diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/filter/CatalogFilter.java b/src/main/java/org/eclipsefoundation/marketplace/dto/filter/CatalogFilter.java index 86e46632232873ab2647791a0ef828b3783b2366..cb4df1a65ba217dce1d2955acb4536024d19d70b 100644 --- a/src/main/java/org/eclipsefoundation/marketplace/dto/filter/CatalogFilter.java +++ b/src/main/java/org/eclipsefoundation/marketplace/dto/filter/CatalogFilter.java @@ -30,10 +30,7 @@ public class CatalogFilter implements DtoFilter<Catalog> { @Override public List<Bson> getFilters(RequestWrapper qps) { - List<Bson> filters = new ArrayList<>(); - - - return filters; + return Collections.emptyList(); } @Override diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/filter/ListingFilter.java b/src/main/java/org/eclipsefoundation/marketplace/dto/filter/ListingFilter.java index b3bc7305d205e4896e7d4e622ffd3787e78369bd..91ab362a268f5d2d765780b83891be6781b43577 100644 --- a/src/main/java/org/eclipsefoundation/marketplace/dto/filter/ListingFilter.java +++ b/src/main/java/org/eclipsefoundation/marketplace/dto/filter/ListingFilter.java @@ -15,8 +15,8 @@ import javax.enterprise.context.ApplicationScoped; import org.bson.conversions.Bson; import org.eclipsefoundation.marketplace.dto.Listing; import org.eclipsefoundation.marketplace.model.RequestWrapper; +import org.eclipsefoundation.marketplace.namespace.DatabaseFieldNames; import org.eclipsefoundation.marketplace.namespace.DtoTableNames; -import org.eclipsefoundation.marketplace.namespace.MongoFieldNames; import org.eclipsefoundation.marketplace.namespace.UrlParameterNames; import com.mongodb.client.model.Aggregates; @@ -35,49 +35,53 @@ import com.mongodb.client.model.Filters; * <li>tags * </ul> * + * <p> + * Injects categories into the results by way of aggregate pipeline. + * </p> + * * @author Martin Lowe */ @ApplicationScoped public class ListingFilter implements DtoFilter<Listing> { @Override - public List<Bson> getFilters(RequestWrapper qps) { + public List<Bson> getFilters(RequestWrapper wrap) { List<Bson> filters = new ArrayList<>(); // Listing ID check - Optional<String> id = qps.getFirstParam(MongoFieldNames.LISTING_ID); + Optional<String> id = wrap.getFirstParam(UrlParameterNames.ID); if (id.isPresent()) { - filters.add(Filters.eq(MongoFieldNames.LISTING_ID, Long.valueOf(id.get()))); + filters.add(Filters.eq(DatabaseFieldNames.DOCID, id.get())); } // select by multiple IDs - List<String> ids = qps.getParams(UrlParameterNames.IDS); + List<String> ids = wrap.getParams(UrlParameterNames.IDS); if (!ids.isEmpty()) { - filters.add(Filters.in(MongoFieldNames.LISTING_ID, ids)); + filters.add(Filters.in(DatabaseFieldNames.DOCID, ids)); } // Listing license type check - Optional<String> licType = qps.getFirstParam(MongoFieldNames.LICENSE_TYPE); + Optional<String> licType = wrap.getFirstParam(DatabaseFieldNames.LICENSE_TYPE); if (licType.isPresent()) { - filters.add(Filters.eq(MongoFieldNames.LICENSE_TYPE, licType.get())); + filters.add(Filters.eq(DatabaseFieldNames.LICENSE_TYPE, licType.get())); } // handle version sub document selection List<Bson> versionFilters = new ArrayList<>(); // solution version - OS filter - Optional<String> os = qps.getFirstParam(UrlParameterNames.OS); + Optional<String> os = wrap.getFirstParam(UrlParameterNames.OS); if (os.isPresent()) { versionFilters.add(Filters.eq("platforms", os.get())); } // solution version - eclipse version - Optional<String> eclipseVersion = qps.getFirstParam(UrlParameterNames.ECLIPSE_VERSION); + Optional<String> eclipseVersion = wrap.getFirstParam(UrlParameterNames.ECLIPSE_VERSION); if (eclipseVersion.isPresent()) { versionFilters.add(Filters.eq("compatible_versions", eclipseVersion.get())); } // TODO this sorts by naturally by character rather than by actual number (e.g. // 1.9 is technically greater than 1.10) // solution version - Java version - Optional<String> javaVersion = qps.getFirstParam(UrlParameterNames.JAVA_VERSION); + Optional<String> javaVersion = wrap.getFirstParam(UrlParameterNames.JAVA_VERSION); if (javaVersion.isPresent()) { versionFilters.add(Filters.gte("min_java_version", javaVersion.get())); } @@ -86,13 +90,13 @@ public class ListingFilter implements DtoFilter<Listing> { } // select by multiple tags - List<String> tags = qps.getParams(UrlParameterNames.TAGS); + List<String> tags = wrap.getParams(UrlParameterNames.TAGS); if (!tags.isEmpty()) { - filters.add(Filters.in(MongoFieldNames.LISTING_TAGS + ".title", tags)); + filters.add(Filters.in(DatabaseFieldNames.LISTING_TAGS + ".title", tags)); } // text search - Optional<String> text = qps.getFirstParam(UrlParameterNames.QUERY_STRING); + Optional<String> text = wrap.getFirstParam(UrlParameterNames.QUERY_STRING); if (text.isPresent()) { filters.add(Filters.text(text.get())); } @@ -103,16 +107,9 @@ public class ListingFilter implements DtoFilter<Listing> { public List<Bson> getAggregates(RequestWrapper wrap) { List<Bson> aggs = new ArrayList<>(); // adds a $lookup aggregate, joining categories on categoryIDS as "categories" - aggs.add(Aggregates.lookup(DtoTableNames.CATEGORY.getTableName(), MongoFieldNames.CATEGORY_IDS, "id", - "categories")); - List<String> marketIdsRaw = wrap.getParams(UrlParameterNames.MARKET_IDS); - List<Integer> marketIds = new ArrayList<>(marketIdsRaw.size()); - try { - marketIdsRaw.forEach(s -> marketIds.add(Integer.valueOf(s))); - } catch (NumberFormatException e) { - // suppress - } - + aggs.add(Aggregates.lookup(DtoTableNames.CATEGORY.getTableName(), DatabaseFieldNames.CATEGORY_IDS, DatabaseFieldNames.DOCID, + DatabaseFieldNames.LISTING_CATEGORIES)); + List<String> marketIds = wrap.getParams(UrlParameterNames.MARKET_IDS); if (!marketIds.isEmpty()) { aggs.add(Aggregates.match(Filters.in("categories.market_ids", marketIds))); } diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/filter/MarketFilter.java b/src/main/java/org/eclipsefoundation/marketplace/dto/filter/MarketFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..7472d7549fe8c474547a536dfc236c4450b5195c --- /dev/null +++ b/src/main/java/org/eclipsefoundation/marketplace/dto/filter/MarketFilter.java @@ -0,0 +1,51 @@ +/* Copyright (c) 2019 Eclipse Foundation and others. + * This program and the accompanying materials are made available + * under the terms of the Eclipse Public License 2.0 + * which is available at http://www.eclipse.org/legal/epl-v20.html, + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipsefoundation.marketplace.dto.filter; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.enterprise.context.ApplicationScoped; + +import org.bson.conversions.Bson; +import org.eclipsefoundation.marketplace.dto.Market; +import org.eclipsefoundation.marketplace.model.RequestWrapper; +import org.eclipsefoundation.marketplace.namespace.DatabaseFieldNames; +import org.eclipsefoundation.marketplace.namespace.DtoTableNames; + +import com.mongodb.client.model.Aggregates; + +/** + * Filter implementation for the {@linkplain Market} class. + * + * @author Martin Lowe + */ +@ApplicationScoped +public class MarketFilter implements DtoFilter<Market> { + + @Override + public List<Bson> getFilters(RequestWrapper wrap) { + return Collections.emptyList(); + } + + @Override + public List<Bson> getAggregates(RequestWrapper wrap) { + List<Bson> aggs = new ArrayList<>(); + // adds a $lookup aggregate, joining categories on categoryIDS as "categories" + aggs.add(Aggregates.lookup(DtoTableNames.CATEGORY.getTableName(), DatabaseFieldNames.CATEGORY_IDS, DatabaseFieldNames.DOCID, + "categories")); + + return aggs; + } + + @Override + public Class<Market> getType() { + return Market.class; + } + +} diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/providers/MarketCodecProvider.java b/src/main/java/org/eclipsefoundation/marketplace/dto/providers/MarketCodecProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..a849c57bdb19078d68937de9f0cabc1fe395d972 --- /dev/null +++ b/src/main/java/org/eclipsefoundation/marketplace/dto/providers/MarketCodecProvider.java @@ -0,0 +1,37 @@ +/* Copyright (c) 2019 Eclipse Foundation and others. + * This program and the accompanying materials are made available + * under the terms of the Eclipse Public License 2.0 + * which is available at http://www.eclipse.org/legal/epl-v20.html, + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipsefoundation.marketplace.dto.providers; + +import org.bson.codecs.Codec; +import org.bson.codecs.configuration.CodecProvider; +import org.bson.codecs.configuration.CodecRegistry; +import org.eclipsefoundation.marketplace.dto.Category; +import org.eclipsefoundation.marketplace.dto.Market; +import org.eclipsefoundation.marketplace.dto.codecs.CategoryCodec; +import org.eclipsefoundation.marketplace.dto.codecs.MarketCodec; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Provides the {@link CategoryCodec} to MongoDB for conversions of + * {@link Category} objects. + * + * @author Martin Lowe + */ +public class MarketCodecProvider implements CodecProvider { + private static final Logger LOGGER = LoggerFactory.getLogger(MarketCodecProvider.class); + + @SuppressWarnings("unchecked") + @Override + public <T> Codec<T> get(Class<T> clazz, CodecRegistry registry) { + if (clazz == Market.class) { + LOGGER.debug("Registering custom Category class MongoDB codec"); + return (Codec<T>) new MarketCodec(); + } + return null; + } +} diff --git a/src/main/java/org/eclipsefoundation/marketplace/model/MongoQuery.java b/src/main/java/org/eclipsefoundation/marketplace/model/MongoQuery.java index 742d111d9f5e7a36a55ce06914057400e1d47921..f1f3aeeec58129ef362849d95304e093ec41e586 100644 --- a/src/main/java/org/eclipsefoundation/marketplace/model/MongoQuery.java +++ b/src/main/java/org/eclipsefoundation/marketplace/model/MongoQuery.java @@ -83,7 +83,6 @@ public class MongoQuery<T> { setSort(sortVal.substring(0, idx), sortVal.substring(idx + 1), filters); } } - LOGGER.error("{}", filters); if (!filters.isEmpty()) { this.filter = Filters.and(filters); } @@ -142,11 +141,8 @@ public class MongoQuery<T> { List<Sortable<?>> fields = SortableHelper.getSortableFields(getDocType()); Optional<Sortable<?>> fieldContainer = SortableHelper.getSortableFieldByName(fields, sortField); - - LOGGER.error("{}:{}", sortField, sortOrder); if (fieldContainer.isPresent()) { this.order = SortOrder.getOrderByName(sortOrder); - LOGGER.error("{}", order); // add sorting query if the sortOrder matches a defined order switch (order) { case RANDOM: diff --git a/src/main/java/org/eclipsefoundation/marketplace/model/RequestWrapper.java b/src/main/java/org/eclipsefoundation/marketplace/model/RequestWrapper.java index 37ded5c837af1acce27acfc5917bf7127b223f1a..341ca9744eeeca2edc4073bf7956fdd10a9061f3 100644 --- a/src/main/java/org/eclipsefoundation/marketplace/model/RequestWrapper.java +++ b/src/main/java/org/eclipsefoundation/marketplace/model/RequestWrapper.java @@ -40,6 +40,7 @@ public class RequestWrapper { private UriInfo uriInfo; private HttpServletRequest request; + private UserAgent userAgent; /** * Generates a wrapper around the @@ -48,6 +49,7 @@ public class RequestWrapper { RequestWrapper() { this.uriInfo = ResteasyContext.getContextData(UriInfo.class); this.request = ResteasyContext.getContextData(HttpServletRequest.class); + this.userAgent = null; } /** @@ -136,4 +138,15 @@ public class RequestWrapper { public Object getAttribute(String key) { return request.getAttribute(key); } + + public String getHeader(String key) { + return request.getHeader(key); + } + + public UserAgent getUserAgent() { + if (userAgent == null) { + this.userAgent = new UserAgent(getHeader("user-agent")); + } + return this.userAgent; + } } diff --git a/src/main/java/org/eclipsefoundation/marketplace/model/UserAgent.java b/src/main/java/org/eclipsefoundation/marketplace/model/UserAgent.java new file mode 100644 index 0000000000000000000000000000000000000000..bc1782286d750660ab91565281a6927811796e09 --- /dev/null +++ b/src/main/java/org/eclipsefoundation/marketplace/model/UserAgent.java @@ -0,0 +1,178 @@ +/* Copyright (c) 2019 Eclipse Foundation and others. + * This program and the accompanying materials are made available + * under the terms of the Eclipse Public License 2.0 + * which is available at http://www.eclipse.org/legal/epl-v20.html, + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipsefoundation.marketplace.model; + +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.google.common.base.Splitter; + +/** + * Custom User-Agent capture that handles and stores information based on MPC + * information. + * + * @author Martin Lowe + */ +public class UserAgent { + + /** + * Matches a generic form of User-Agent strings with the following sections + * + * <code>Agent-Name/Agent-Version (System-Properties;) Platform (Platform-Details) Enhancements</code> + * + * As User-Agents aren't a standardized format, there is leniency on there being + * trailing sections being missing. The only required field is the Agent name + * and version, separated by a slash. + */ + private static final Pattern USER_AGENT_PATTERN = Pattern + .compile("^(\\S+\\/\\S+)\\s?(?:\\(([^\\)]*?)\\)([^\\(]+(?:\\(([^\\)]*?)\\)([^\\(]+)?)?)?)?$"); + private static final String MPC_CLIENT_AGENT_NAME = "mpc"; + + private final String name; + private final String version; + private final String systemProperties; + private final String platform; + private final String platformDetails; + private final String enhancements; + + private String javaVersion; + private String javaVendor; + + private String os; + private String osVersion; + private String locale; + + public UserAgent(String userAgent) { + Objects.requireNonNull(userAgent); + + // check that the user agent matches the expected standard pattern + Matcher m = USER_AGENT_PATTERN.matcher(userAgent); + if (!m.matches()) { + throw new IllegalArgumentException("Passed string does not match an expected user-agent"); + } + + // get the name and version of the user agent + String agentDeclaration = m.group(0); + Iterator<String> it = Splitter.on('/').trimResults().split(agentDeclaration).iterator(); + this.name = it.next(); + this.version = it.next(); + + this.systemProperties = m.group(1); + this.platform = m.group(2); + this.platformDetails = m.group(3); + this.enhancements = m.group(4); + if (MPC_CLIENT_AGENT_NAME.equalsIgnoreCase(name)) { + handleMpc(); + } else if (name.contains("bot")) { + // drop as we don't care about bot properties + } else { + handleWeb(); + } + } + + private void handleMpc() { + List<String> systemPropList = Splitter.on(';').splitToList(systemProperties); + if (systemPropList.size() != 3) { + // TODO throw exception? + } + // expected form example: Java <java version> <vendor> + String javaPropStr = systemPropList.get(0); + String javaPlatform = javaPropStr.substring(javaPropStr.indexOf(' ') + 1); + List<String> javaProps = Splitter.on(' ').limit(2).splitToList(javaPlatform); + this.javaVersion = javaProps.get(0); + this.javaVendor = javaProps.get(1); + + // expected form: <OS name> <OS version> <??> + String systemPlatform = systemPropList.get(1); + this.locale = systemPropList.get(2); + } + + private void handleWeb() { + + } + + /** + * @return the name + */ + public String getName() { + return name; + } + + /** + * @return the version + */ + public String getVersion() { + return version; + } + + /** + * @return the version + */ + public String getSystemProperties() { + return systemProperties; + } + + /** + * @return the platform + */ + public String getPlatform() { + return platform; + } + + /** + * @return the platformDetails + */ + public String getPlatformDetails() { + return platformDetails; + } + + /** + * @return the enhancements + */ + public String getEnhancements() { + return enhancements; + } + + /** + * @return the javaVersion + */ + public String getJavaVersion() { + return javaVersion; + } + + /** + * @return the javaVendor + */ + public String getJavaVendor() { + return javaVendor; + } + + /** + * @return the os + */ + public String getOs() { + return os; + } + + /** + * @return the osVersion + */ + public String getOsVersion() { + return osVersion; + } + + /** + * @return the locale + */ + public String getLocale() { + return locale; + } + +} diff --git a/src/main/java/org/eclipsefoundation/marketplace/namespace/MongoFieldNames.java b/src/main/java/org/eclipsefoundation/marketplace/namespace/DatabaseFieldNames.java similarity index 90% rename from src/main/java/org/eclipsefoundation/marketplace/namespace/MongoFieldNames.java rename to src/main/java/org/eclipsefoundation/marketplace/namespace/DatabaseFieldNames.java index 408e075285170529d05076c7f2d907a4189f8c2d..2fd71bc80187cd910ec4ef57ef2f24223f2d5b84 100644 --- a/src/main/java/org/eclipsefoundation/marketplace/namespace/MongoFieldNames.java +++ b/src/main/java/org/eclipsefoundation/marketplace/namespace/DatabaseFieldNames.java @@ -15,12 +15,14 @@ package org.eclipsefoundation.marketplace.namespace; * @author Martin Lowe * */ -public final class MongoFieldNames { +public final class DatabaseFieldNames { - public static final String DOCID = "id"; - public static final String LISTING_ID = "listing_id"; + // base fields + public static final String DOCID = "_id"; + public static final String URL = "url"; + + // listing fields public static final String LISTING_TITLE = "title"; - public static final String LISTING_URL = "url"; public static final String LISTING_TEASER = "teaser"; public static final String LISTING_BODY = "body"; public static final String LISTING_AUTHORS = "authors"; @@ -57,6 +59,8 @@ public final class MongoFieldNames { public static final String CATEGORY_NAME = "name"; public static final String CATEGORY_URL = "url"; - private MongoFieldNames() { + public static final String MARKET_NAME = "name"; + + private DatabaseFieldNames() { } } diff --git a/src/main/java/org/eclipsefoundation/marketplace/namespace/DtoTableNames.java b/src/main/java/org/eclipsefoundation/marketplace/namespace/DtoTableNames.java index 4c40791c330a03ca4e8238f867bf37ca341591da..7b90e763e1a22d5351a1211af39fbfd2e77b024a 100644 --- a/src/main/java/org/eclipsefoundation/marketplace/namespace/DtoTableNames.java +++ b/src/main/java/org/eclipsefoundation/marketplace/namespace/DtoTableNames.java @@ -9,6 +9,7 @@ package org.eclipsefoundation.marketplace.namespace; import org.eclipsefoundation.marketplace.dto.Catalog; import org.eclipsefoundation.marketplace.dto.Category; import org.eclipsefoundation.marketplace.dto.Listing; +import org.eclipsefoundation.marketplace.dto.Market; /** * Mapping of DTO classes to their respective tables in the DB. @@ -19,7 +20,7 @@ import org.eclipsefoundation.marketplace.dto.Listing; public enum DtoTableNames { LISTING(Listing.class, "listings"), CATEGORY(Category.class, "categories"), - CATALOG(Catalog.class, "catalogs"); + CATALOG(Catalog.class, "catalogs"), MARKET(Market.class, "markets"); private Class<?> baseClass; private String tableName; diff --git a/src/main/java/org/eclipsefoundation/marketplace/namespace/UrlParameterNames.java b/src/main/java/org/eclipsefoundation/marketplace/namespace/UrlParameterNames.java index 6fc96cd1c2d0661b0839d9745b714b4af37263d7..feb92164115d415fe4fab60cd4e362e501385a18 100644 --- a/src/main/java/org/eclipsefoundation/marketplace/namespace/UrlParameterNames.java +++ b/src/main/java/org/eclipsefoundation/marketplace/namespace/UrlParameterNames.java @@ -21,6 +21,7 @@ public final class UrlParameterNames { public static final String IDS = "ids"; public static final String TAGS = "tags"; public static final String MARKET_IDS = "market_ids"; + public static final String ID = "id"; private UrlParameterNames() { } diff --git a/src/main/java/org/eclipsefoundation/marketplace/resource/ListingResource.java b/src/main/java/org/eclipsefoundation/marketplace/resource/ListingResource.java index c2368b1a557e011479272e7dd46ad1442a2b00cf..945c32e66737b56359bc69c3e08d5500b6a1d462 100644 --- a/src/main/java/org/eclipsefoundation/marketplace/resource/ListingResource.java +++ b/src/main/java/org/eclipsefoundation/marketplace/resource/ListingResource.java @@ -31,7 +31,7 @@ import org.eclipsefoundation.marketplace.helper.StreamHelper; import org.eclipsefoundation.marketplace.model.MongoQuery; import org.eclipsefoundation.marketplace.model.RequestWrapper; import org.eclipsefoundation.marketplace.model.ResourceDataType; -import org.eclipsefoundation.marketplace.namespace.MongoFieldNames; +import org.eclipsefoundation.marketplace.namespace.UrlParameterNames; import org.eclipsefoundation.marketplace.service.CachingService; import org.jboss.resteasy.annotations.jaxrs.PathParam; import org.slf4j.Logger; @@ -89,7 +89,7 @@ public class ListingResource { */ @POST public Response postListing(Listing listing) { - MongoQuery<Listing> q = new MongoQuery<>(params, dtoFilter,cachingService); + MongoQuery<Listing> q = new MongoQuery<>(params, dtoFilter, cachingService); // add the object, and await the result StreamHelper.awaitCompletionStage(dao.add(q, Arrays.asList(listing))); @@ -102,17 +102,17 @@ public class ListingResource { * Endpoint for /listing/\<listingId\> to retrieve a specific listing from the * database. * - * @param listingId int version of the listing ID + * @param listingId the listing ID * @return response for the browser */ @GET @Path("/{listingId}") - public Response select(@PathParam("listingId") int listingId) { - params.addParam(MongoFieldNames.LISTING_ID, Integer.toString(listingId)); + public Response select(@PathParam("listingId") String listingId) { + params.addParam(UrlParameterNames.ID, listingId); MongoQuery<Listing> q = new MongoQuery<>(params, dtoFilter, cachingService); // retrieve a cached version of the value for the current listing - Optional<List<Listing>> cachedResults = cachingService.get(Integer.toString(listingId), params, + Optional<List<Listing>> cachedResults = cachingService.get(listingId, params, () -> StreamHelper.awaitCompletionStage(dao.get(q))); if (!cachedResults.isPresent()) { LOGGER.error("Error while retrieving cached listing for ID {}", listingId); @@ -132,7 +132,7 @@ public class ListingResource { */ @GET @Path("/{listingId}/installs") - public Response selectInstallMetrics(@PathParam("listingId") int listingId) { + public Response selectInstallMetrics(@PathParam("listingId") String listingId) { throw new UnsupportedOperationException("Getting install statistics is not yet supported"); } @@ -147,7 +147,7 @@ public class ListingResource { */ @GET @Path("/{listingId}/versions/{version}/installs") - public Response selectInstallMetrics(@PathParam("listingId") int listingId, @PathParam("version") int version) { + public Response selectInstallMetrics(@PathParam("listingId") String listingId, @PathParam("version") int version) { throw new UnsupportedOperationException("Getting install statistics is not yet supported"); } @@ -161,7 +161,7 @@ public class ListingResource { */ @POST @Path("/{listingId}/versions/{version}/installs") - public Response postInstallMetrics(@PathParam("listingId") int listingId, @PathParam("version") int version, + public Response postInstallMetrics(@PathParam("listingId") String listingId, @PathParam("version") String version, Install installDetails) { return Response.ok(installDetails).build(); } diff --git a/src/main/java/org/eclipsefoundation/marketplace/resource/MarketResource.java b/src/main/java/org/eclipsefoundation/marketplace/resource/MarketResource.java new file mode 100644 index 0000000000000000000000000000000000000000..d91451faeea5c8bd839644bbbaa3f79c9cbc4a07 --- /dev/null +++ b/src/main/java/org/eclipsefoundation/marketplace/resource/MarketResource.java @@ -0,0 +1,113 @@ +/* Copyright (c) 2019 Eclipse Foundation and others. + * This program and the accompanying materials are made available + * under the terms of the Eclipse Public License 2.0 + * which is available at http://www.eclipse.org/legal/epl-v20.html, + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipsefoundation.marketplace.resource; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.eclipsefoundation.marketplace.dao.MongoDao; +import org.eclipsefoundation.marketplace.dto.Market; +import org.eclipsefoundation.marketplace.dto.filter.DtoFilter; +import org.eclipsefoundation.marketplace.helper.StreamHelper; +import org.eclipsefoundation.marketplace.model.MongoQuery; +import org.eclipsefoundation.marketplace.model.RequestWrapper; +import org.eclipsefoundation.marketplace.model.ResourceDataType; +import org.eclipsefoundation.marketplace.namespace.UrlParameterNames; +import org.eclipsefoundation.marketplace.service.CachingService; +import org.jboss.resteasy.annotations.jaxrs.PathParam; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author martin + * + */ +@ResourceDataType(Market.class) +@Path("/markets") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +@RequestScoped +public class MarketResource { + private static final Logger LOGGER = LoggerFactory.getLogger(MarketResource.class); + + @Inject + MongoDao dao; + @Inject + CachingService<List<Market>> cachingService; + @Inject + RequestWrapper params; + @Inject + DtoFilter<Market> dtoFilter; + + @GET + public Response select() { + MongoQuery<Market> q = new MongoQuery<>(params, dtoFilter, cachingService); + // retrieve the possible cached object + Optional<List<Market>> cachedResults = cachingService.get("all", params, + () -> StreamHelper.awaitCompletionStage(dao.get(q))); + if (!cachedResults.isPresent()) { + LOGGER.error("Error while retrieving cached Categorys"); + return Response.serverError().build(); + } + + // return the results as a response + return Response.ok(cachedResults.get()).build(); + } + + /** + * Endpoint for /markets/ to post a new Market to the persistence layer. + * + * @param market the Category object to insert into the database. + * @return response for the browser + */ + @POST + public Response postMarket(Market market) { + MongoQuery<Market> q = new MongoQuery<>(params, dtoFilter, cachingService); + + // add the object, and await the result + StreamHelper.awaitCompletionStage(dao.add(q, Arrays.asList(market))); + + // return the results as a response + return Response.ok().build(); + } + + /** + * Endpoint for /markets/\<marketId\> to retrieve a specific Market from the + * database. + * + * @param marketId int version of the listing ID + * @return response for the browser + */ + @GET + @Path("/{marketId}") + public Response select(@PathParam("marketId") String marketId) { + params.addParam(UrlParameterNames.ID, marketId); + + MongoQuery<Market> q = new MongoQuery<>(params, dtoFilter, cachingService); + // retrieve a cached version of the value for the current listing + Optional<List<Market>> cachedResults = cachingService.get(marketId, params, + () -> StreamHelper.awaitCompletionStage(dao.get(q))); + if (!cachedResults.isPresent()) { + LOGGER.error("Error while retrieving cached listing for ID {}", marketId); + return Response.serverError().build(); + } + + // return the results as a response + return Response.ok(cachedResults.get()).build(); + } +} diff --git a/src/main/node/hammer.js b/src/main/node/hammer.js new file mode 100644 index 0000000000000000000000000000000000000000..9778db00d5b4903106e137a7ee73b417bdf30ced --- /dev/null +++ b/src/main/node/hammer.js @@ -0,0 +1,51 @@ +const argv = require('yargs') + .option('c', { + description: 'Number of calls to queue', + alias: 'count', + default: 100, + nargs: 1 + }) + .option('s', { + description: 'Server address (e.g. http://localhost:8090)', + alias: 'server', + nargs: 1, + demandOption: true + }).argv; +const axios = require('axios'); + +var start = new Date(); +var arr = []; +var dupes = 0; +var promises = []; +for (var i=0;i<argv.c;i++) { + promises.push(axios.get(`${argv.s}/listings?c=${Math.random()}`) + .then(result => report(result)) + .catch(err => console.log(err))); +} + + + +toodles(); +async function toodles() { + try { + await Promise.all(promises) + } catch (error) { + this.errormsg = error.message; + } finally { + this.loading = false + console.log(`Unique entires: ${arr.length}: duplicates: ${dupes}`) + var end = new Date(); + console.log(`Start: ${start}, end: ${end}`) + } +} +function report(result) { + var d = result.data; + for (l in d) { + var id = d[l].id; + if (arr.indexOf(id) == -1) { + arr.push(id); + } else { + dupes++; + } + } +} \ No newline at end of file diff --git a/src/main/node/index.js b/src/main/node/index.js index 4f7af38dea0e7e71eafa1c93154fc74d4dc5ebd6..fd12f21a14421a895dbe419d888c2bc48193c447 100644 --- a/src/main/node/index.js +++ b/src/main/node/index.js @@ -1,5 +1,6 @@ const axios = require('axios'); const randomWords = require('random-words'); +const uuid = require('uuid'); const argv = require('yargs') .option('c', { description: 'Number of listings to generate', @@ -19,11 +20,18 @@ const lic_types = ["EPL-2.0","EPL-1.0","GPL"]; const platforms = ["windows","macos","linux"]; const eclipseVs = ["4.6","4.7","4.8","4.9","4.10","4.11","4.12"]; const javaVs = ["1.5", "1.6", "1.7", "1.8", "1.9", "1.10"]; -const categoryIds = [...Array(20).keys()]; -const marketIds = [...Array(5).keys()]; +const categoryIds = []; +for (var i=0;i<20;i++) { + categoryIds.push(uuid.v4()); +} +const marketIds = []; +for (var i=0;i<5;i++) { + marketIds.push(uuid.v4()); +} createListing(0); createCategory(0); +createMarket(0); function shuff(arr) { var out = Array.from(arr); @@ -51,22 +59,32 @@ function createListing(count) { if (count >= max) { return; } - - axios.post(argv.s+"/listings/", generateJSON(count++)) + count++; + axios.post(argv.s+"/listings/", generateJSON(uuid.v4())) .then(createListing(count)) .catch(err => console.log(err)); } function createCategory(count) { - if (count >= 20) { + if (count >= categoryIds.length) { return; } - axios.post(argv.s+"/categories/", generateCategoryJSON(count++)) + axios.post(argv.s+"/categories/", generateCategoryJSON(categoryIds[count++])) .then(createCategory(count)) .catch(err => console.log(err)); } +function createMarket(count) { + if (count >= marketIds.length) { + return; + } + + axios.post(argv.s+"/markets/", generateMarketJSON(marketIds[count++])) + .then(createMarket(count)) + .catch(err => console.log(err)); +} + function generateJSON(id) { var solutions = []; var solsCount = Math.floor(Math.random()*5); @@ -80,7 +98,6 @@ function generateJSON(id) { } return { - "listing_id": id, "title": "Sample", "url": "https://jakarta.ee", "foundation_ember": false, @@ -113,15 +130,23 @@ function generateJSON(id) { } ], "versions": solutions, - "category_ids": splice(categoryIds) + "category_ids": splice(categoryIds).splice(0,Math.ceil(Math.random()*5)+1) }; } function generateCategoryJSON(id) { + return { + "id": id, + "name": randomWords({exactly:1, wordsPerString:Math.ceil(Math.random()*4)})[0], + "url": "https://www.eclipse.org" + }; +} + +function generateMarketJSON(id) { return { "id": id, "name": randomWords({exactly:1, wordsPerString:Math.ceil(Math.random()*4)})[0], "url": "https://www.eclipse.org", - "market_ids": [splice(marketIds)[0]] + "category_ids": splice(categoryIds).splice(0,Math.ceil(Math.random()*5)+1) }; } diff --git a/src/test/java/org/eclipsefoundation/marketplace/helper/SortableHelperTest.java b/src/test/java/org/eclipsefoundation/marketplace/helper/SortableHelperTest.java index fc6a73aca3e985e6ab3ef2b234aca3bf0cdf817e..f6b07e61c51c3070a94cd7941b60c3ba69ce3329 100644 --- a/src/test/java/org/eclipsefoundation/marketplace/helper/SortableHelperTest.java +++ b/src/test/java/org/eclipsefoundation/marketplace/helper/SortableHelperTest.java @@ -9,6 +9,7 @@ package org.eclipsefoundation.marketplace.helper; import java.util.List; import java.util.Optional; +import org.eclipsefoundation.marketplace.helper.SortableHelper; import org.eclipsefoundation.marketplace.helper.SortableHelper.Sortable; import org.eclipsefoundation.marketplace.model.SortableField; import org.junit.jupiter.api.Assertions; diff --git a/src/test/java/org/eclipsefoundation/marketplace/model/RequestWrapperMock.java b/src/test/java/org/eclipsefoundation/marketplace/model/RequestWrapperMock.java new file mode 100644 index 0000000000000000000000000000000000000000..7549e7f90e8566b9ff4ce57c37212ead77a631bb --- /dev/null +++ b/src/test/java/org/eclipsefoundation/marketplace/model/RequestWrapperMock.java @@ -0,0 +1,18 @@ +/* Copyright (c) 2019 Eclipse Foundation and others. + * This program and the accompanying materials are made available + * under the terms of the Eclipse Public License 2.0 + * which is available at http://www.eclipse.org/legal/epl-v20.html, + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipsefoundation.marketplace.model; + +import org.eclipsefoundation.marketplace.model.RequestWrapper; + +/** + * Wraps RequestWrapper for access outside of request scope in tests. + * + * @author Martin Lowe + */ +public class RequestWrapperMock extends RequestWrapper { + +} diff --git a/src/test/java/org/eclipsefoundation/marketplace/service/impl/GuavaCachingServiceTest.java b/src/test/java/org/eclipsefoundation/marketplace/service/impl/GuavaCachingServiceTest.java index 0da665bc8c3bb122332fa5b3aa8dcb771b03fbe8..54f802f3edf9f40a2715ede04fbb2dc455a5ebd7 100644 --- a/src/test/java/org/eclipsefoundation/marketplace/service/impl/GuavaCachingServiceTest.java +++ b/src/test/java/org/eclipsefoundation/marketplace/service/impl/GuavaCachingServiceTest.java @@ -9,26 +9,30 @@ package org.eclipsefoundation.marketplace.service.impl; import java.util.Optional; import javax.inject.Inject; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.core.UriInfo; import org.eclipsefoundation.marketplace.model.RequestWrapper; +import org.eclipsefoundation.marketplace.model.RequestWrapperMock; +import org.eclipsefoundation.marketplace.service.impl.GuavaCachingService; +import org.jboss.resteasy.core.ResteasyContext; +import org.jboss.resteasy.specimpl.ResteasyUriInfo; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import io.quarkus.test.junit.DisabledOnSubstrate; import io.quarkus.test.junit.QuarkusTest; +import io.undertow.servlet.spec.HttpServletRequestImpl; /** * @author martin * */ -@DisabledOnSubstrate @QuarkusTest public class GuavaCachingServiceTest { @Inject GuavaCachingService<Object> gcs; - @Inject RequestWrapper sample; /** @@ -36,6 +40,11 @@ public class GuavaCachingServiceTest { */ @BeforeEach public void pre() { + // inject empty objects into the Request context before creating a mock object + ResteasyContext.pushContext(UriInfo.class, new ResteasyUriInfo("","")); + ResteasyContext.pushContext(HttpServletRequest.class, new HttpServletRequestImpl(null, null)); + + this.sample = new RequestWrapperMock(); // expire all active key values gcs.removeAll(); }