diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/ErrorReport.java b/src/main/java/org/eclipsefoundation/marketplace/dto/ErrorReport.java new file mode 100644 index 0000000000000000000000000000000000000000..5e1f2a33b555f156b1dac7918540e78109f1ca40 --- /dev/null +++ b/src/main/java/org/eclipsefoundation/marketplace/dto/ErrorReport.java @@ -0,0 +1,145 @@ +/* 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; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents an error report in the database. + * + * @author Martin Lowe + */ +public class ErrorReport { + + private String id; + private String title; + private String body; + private String status; + private String statusMessage; + private List<String> featureIDs; + private String detailedMessage; + private boolean read; + private String listingId; + + /** + * + */ + public ErrorReport() { + this.featureIDs = new ArrayList<>(); + } + + /** + * @return the id + */ + public String getId() { + return id; + } + /** + * @param id the id to set + */ + public void setId(String id) { + this.id = id; + } + /** + * @return the title + */ + public String getTitle() { + return title; + } + /** + * @param title the title to set + */ + public void setTitle(String title) { + this.title = title; + } + /** + * @return the body + */ + public String getBody() { + return body; + } + /** + * @param body the body to set + */ + public void setBody(String body) { + this.body = body; + } + /** + * @return the status + */ + public String getStatus() { + return status; + } + /** + * @param status the status to set + */ + public void setStatus(String status) { + this.status = status; + } + /** + * @return the statusMessage + */ + public String getStatusMessage() { + return statusMessage; + } + /** + * @param statusMessage the statusMessage to set + */ + public void setStatusMessage(String statusMessage) { + this.statusMessage = statusMessage; + } + /** + * @return the featureIDs + */ + public List<String> getFeatureIDs() { + return new ArrayList<>(featureIDs); + } + /** + * @param featureIDs the featureIDs to set + */ + public void setFeatureIds(List<String> featureIDs) { + this.featureIDs = new ArrayList<>(featureIDs); + } + /** + * @return the detailedMessage + */ + public String getDetailedMessage() { + return detailedMessage; + } + /** + * @param detailedMessage the detailedMessage to set + */ + public void setDetailedMessage(String detailedMessage) { + this.detailedMessage = detailedMessage; + } + /** + * @return the read + */ + public boolean isRead() { + return read; + } + /** + * @param read the read to set + */ + public void setRead(boolean read) { + this.read = read; + } + /** + * @return the listingId + */ + public String getListingId() { + return listingId; + } + /** + * @param listingId the listingId to set + */ + public void setListingId(String listingId) { + this.listingId = listingId; + } + +} diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/ErrorReportCodec.java b/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/ErrorReportCodec.java new file mode 100644 index 0000000000000000000000000000000000000000..ac2915a6e192399df5f05457991605237a41c024 --- /dev/null +++ b/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/ErrorReportCodec.java @@ -0,0 +1,95 @@ +/* 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.ErrorReport; +import org.eclipsefoundation.marketplace.namespace.DatabaseFieldNames; + +import com.mongodb.MongoClient; + + +/** + * MongoDB codec for transcoding of {@link ErrorReport} and {@link Document} + * objects. Used when writing or retrieving objects of given type from the + * database. + * + * @author Martin Lowe + */ +public class ErrorReportCodec implements CollectibleCodec<ErrorReport> { + private final Codec<Document> documentCodec; + + public ErrorReportCodec() { + this.documentCodec = MongoClient.getDefaultCodecRegistry().get(Document.class); + } + + @Override + public void encode(BsonWriter writer, ErrorReport value, EncoderContext encoderContext) { + Document doc = new Document(); + + doc.put(DatabaseFieldNames.DOCID, value.getId()); + doc.put(DatabaseFieldNames.NAME, value.getTitle()); + doc.put(DatabaseFieldNames.ERROR_BODY, value.getBody()); + doc.put(DatabaseFieldNames.ERROR_DETAILED_MESSAGE, value.getDetailedMessage()); + doc.put(DatabaseFieldNames.ERROR_READ, value.isRead()); + doc.put(DatabaseFieldNames.ERROR_FEATURE_IDS, value.getFeatureIDs()); + doc.put(DatabaseFieldNames.ERROR_STATUS_CODE, value.getStatus()); + doc.put(DatabaseFieldNames.ERROR_STATUS_MESSAGE, value.getStatusMessage()); + documentCodec.encode(writer, doc, encoderContext); + } + + @Override + public Class<ErrorReport> getEncoderClass() { + return ErrorReport.class; + } + + @Override + public ErrorReport decode(BsonReader reader, DecoderContext decoderContext) { + Document document = documentCodec.decode(reader, decoderContext); + ErrorReport out = new ErrorReport(); + out.setId(document.getString(DatabaseFieldNames.DOCID)); + out.setTitle(document.getString(DatabaseFieldNames.ERROR_TITLE)); + out.setBody(document.getString(DatabaseFieldNames.ERROR_BODY)); + out.setDetailedMessage(document.getString(DatabaseFieldNames.ERROR_DETAILED_MESSAGE)); + out.setStatusMessage(document.getString(DatabaseFieldNames.ERROR_STATUS_MESSAGE)); + out.setStatus(document.getString(DatabaseFieldNames.ERROR_STATUS_CODE)); + out.setFeatureIds(document.getList(DatabaseFieldNames.ERROR_FEATURE_IDS, String.class)); + out.setRead(document.getBoolean(DatabaseFieldNames.ERROR_READ)); + + return out; + } + + @Override + public ErrorReport generateIdIfAbsentFromDocument(ErrorReport document) { + if (!documentHasId(document)) { + document.setId(UUID.randomUUID().toString()); + } + return document; + } + + @Override + public boolean documentHasId(ErrorReport document) { + return !StringUtils.isBlank(document.getId()); + } + + @Override + public BsonValue getDocumentId(ErrorReport document) { + 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 53bb117ae40b267786d533718ac4f21b49b0da2e..59dc2f055bdc2709342d499ca3e5f7a57a37f39c 100644 --- a/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/ListingCodec.java +++ b/src/main/java/org/eclipsefoundation/marketplace/dto/codecs/ListingCodec.java @@ -102,7 +102,7 @@ public class ListingCodec implements CollectibleCodec<Listing> { public Listing decode(BsonReader reader, DecoderContext decoderContext) { Document document = documentCodec.decode(reader, decoderContext); Listing out = new Listing(); - + // for each field, get the value from the encoded object and set it in POJO out.setId(document.getString(DatabaseFieldNames.DOCID)); out.setName(document.getString(DatabaseFieldNames.NAME)); diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/filter/DtoFilter.java b/src/main/java/org/eclipsefoundation/marketplace/dto/filter/DtoFilter.java index 305032608c67508e3b3bddac7fe9254cb39f617a..e5c6d5c064ea6dea6d0da4a062362a08806fd89b 100644 --- a/src/main/java/org/eclipsefoundation/marketplace/dto/filter/DtoFilter.java +++ b/src/main/java/org/eclipsefoundation/marketplace/dto/filter/DtoFilter.java @@ -12,14 +12,32 @@ import org.bson.conversions.Bson; import org.eclipsefoundation.marketplace.model.RequestWrapper; /** - * @author martin - * + * Filter interface for usage when querying data. + * + * @author Martin Lowe */ public interface DtoFilter<T> { + /** + * Retrieve filter objects for the current arguments. + * + * @param wrap wrapper for the current request + * @return list of filters for the current request, or empty if there are no applicable filters. + */ List<Bson> getFilters(RequestWrapper wrap); - + + /** + * Retrieve aggregate filter operations for the current arguments. + * + * @param wrap wrapper for the current request + * @return list of aggregates for the current request, or empty if there are no applicable aggregates. + */ List<Bson> getAggregates(RequestWrapper wrap); - + + /** + * Returns the type of data this object will filter for. + * + * @return class of object to filter + */ Class<T> getType(); } diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/filter/ErrorReportFilter.java b/src/main/java/org/eclipsefoundation/marketplace/dto/filter/ErrorReportFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..51a30bec9d03f77c8c4c979e3246949809343594 --- /dev/null +++ b/src/main/java/org/eclipsefoundation/marketplace/dto/filter/ErrorReportFilter.java @@ -0,0 +1,82 @@ +/* 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 java.util.Optional; + +import javax.enterprise.context.ApplicationScoped; + +import org.bson.conversions.Bson; +import org.eclipsefoundation.marketplace.dto.ErrorReport; +import org.eclipsefoundation.marketplace.model.RequestWrapper; +import org.eclipsefoundation.marketplace.namespace.DatabaseFieldNames; +import org.eclipsefoundation.marketplace.namespace.UrlParameterNames; + +import com.mongodb.client.model.Filters; + +/** + * Filter implementation for the ErrorReport class. + * + * @author Martin Lowe + */ +@ApplicationScoped +public class ErrorReportFilter implements DtoFilter<ErrorReport> { + + @Override + public List<Bson> getFilters(RequestWrapper wrap) { + List<Bson> filters = new ArrayList<>(); + + // ErrorReport ID check + Optional<String> id = wrap.getFirstParam(UrlParameterNames.ID); + if (id.isPresent()) { + filters.add(Filters.eq(DatabaseFieldNames.DOCID, id.get())); + } + + // select by multiple IDs + List<String> ids = wrap.getParams(UrlParameterNames.IDS); + if (!ids.isEmpty()) { + filters.add(Filters.in(DatabaseFieldNames.DOCID, ids)); + } + + // listing ID check + Optional<String> listingId = wrap.getFirstParam(UrlParameterNames.LISTING_ID); + if (listingId.isPresent()) { + filters.add(Filters.eq(DatabaseFieldNames.LISTING_ID, listingId.get())); + } + + // listing ID check + Optional<String> isRead = wrap.getFirstParam(UrlParameterNames.READ); + if (isRead.isPresent()) { + filters.add(Filters.eq(DatabaseFieldNames.ERROR_READ, Boolean.valueOf(isRead.get()))); + } + + // select by feature ID + List<String> featureId = wrap.getParams(UrlParameterNames.FEATURE_ID); + if (!featureId.isEmpty()) { + filters.add(Filters.in(DatabaseFieldNames.ERROR_FEATURE_IDS, featureId)); + } + // text search + Optional<String> text = wrap.getFirstParam(UrlParameterNames.QUERY_STRING); + if (text.isPresent()) { + filters.add(Filters.text(text.get())); + } + return filters; + } + + @Override + public List<Bson> getAggregates(RequestWrapper wrap) { + return Collections.emptyList(); + } + + @Override + public Class<ErrorReport> getType() { + return ErrorReport.class; + } +} diff --git a/src/main/java/org/eclipsefoundation/marketplace/dto/providers/ErrorReportCodecProvider.java b/src/main/java/org/eclipsefoundation/marketplace/dto/providers/ErrorReportCodecProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..f8b605aa8ee475e98973333a8caa03d84c6d477b --- /dev/null +++ b/src/main/java/org/eclipsefoundation/marketplace/dto/providers/ErrorReportCodecProvider.java @@ -0,0 +1,35 @@ +/* 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.ErrorReport; +import org.eclipsefoundation.marketplace.dto.codecs.ErrorReportCodec; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Provides the {@link ErrorReportCodec} to MongoDB for conversions of + * {@link ErrorReport} objects. + * + * @author Martin Lowe + */ +public class ErrorReportCodecProvider implements CodecProvider { + private static final Logger LOGGER = LoggerFactory.getLogger(ErrorReportCodecProvider.class); + + @SuppressWarnings("unchecked") + @Override + public <T> Codec<T> get(Class<T> clazz, CodecRegistry registry) { + if (clazz == ErrorReport.class) { + LOGGER.debug("Registering custom ErrorReport class MongoDB codec"); + return (Codec<T>) new ErrorReportCodec(); + } + return null; + } +} diff --git a/src/main/java/org/eclipsefoundation/marketplace/namespace/DatabaseFieldNames.java b/src/main/java/org/eclipsefoundation/marketplace/namespace/DatabaseFieldNames.java index 9638a310f0b82cae15586ac7a9fc984673df4590..c375a6ca31ac449b877e31bcc9e8b9eb9762a06d 100644 --- a/src/main/java/org/eclipsefoundation/marketplace/namespace/DatabaseFieldNames.java +++ b/src/main/java/org/eclipsefoundation/marketplace/namespace/DatabaseFieldNames.java @@ -22,6 +22,7 @@ public final class DatabaseFieldNames { public static final String URL = "url"; public static final String NAME = "name"; public static final String OS = "os"; + public static final String LISTING_ID = "listing_id"; // listing fields public static final String LISTING_TEASER = "teaser"; @@ -44,6 +45,7 @@ public final class DatabaseFieldNames { public static final String LISTING_TAGS = "tags"; public static final String CATEGORY_IDS = "category_ids"; public static final String LISTING_CATEGORIES = "categories"; + public static final String FEATURE_IDS = "feature_ids"; public static final String FEATURE_ID = "feature_id"; public static final String INSTALL_STATE = "install_state"; @@ -58,13 +60,15 @@ public final class DatabaseFieldNames { // category fields public static final String MARKET_IDS = "market_ids"; - public static final String INSTALL_JAVA_VERSION = "java_version"; - public static final String INSTALL_VERSION = "version"; - public static final String INSTALL_LISTING_ID = "listing_id"; - public static final String INSTALL_DATE = "date"; - public static final String ECLIPSE_VERSION = "eclipse_version"; - public static final String FEATURE_IDS = "feature_ids"; - public static final String LOCALE = "locale"; + // errorreport fields + public static final String ERROR_TITLE = "title"; + public static final String ERROR_BODY = "body"; + public static final String ERROR_DETAILED_MESSAGE = "detailed_message"; + public static final String ERROR_READ = "read"; + public static final String ERROR_FEATURE_IDS = "feature_ids"; + public static final String ERROR_IP_ADDRESS = "ip_address"; + public static final String ERROR_STATUS_CODE = "status_code"; + public static final String ERROR_STATUS_MESSAGE = "status_message"; 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 7b90e763e1a22d5351a1211af39fbfd2e77b024a..bcbb5cb25815de9b7b401698b3d7a3ce11bd264f 100644 --- a/src/main/java/org/eclipsefoundation/marketplace/namespace/DtoTableNames.java +++ b/src/main/java/org/eclipsefoundation/marketplace/namespace/DtoTableNames.java @@ -10,6 +10,7 @@ import org.eclipsefoundation.marketplace.dto.Catalog; import org.eclipsefoundation.marketplace.dto.Category; import org.eclipsefoundation.marketplace.dto.Listing; import org.eclipsefoundation.marketplace.dto.Market; +import org.eclipsefoundation.marketplace.dto.ErrorReport; /** * Mapping of DTO classes to their respective tables in the DB. @@ -20,7 +21,7 @@ import org.eclipsefoundation.marketplace.dto.Market; public enum DtoTableNames { LISTING(Listing.class, "listings"), CATEGORY(Category.class, "categories"), - CATALOG(Catalog.class, "catalogs"), MARKET(Market.class, "markets"); + CATALOG(Catalog.class, "catalogs"), MARKET(Market.class, "markets"),ERRORREPORT(ErrorReport.class, "errorreports"); 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 6d9791cb9fecb0843d5bc6aee96d0806a3566ca0..8ebdc7f33cd7a052d4f3131685759d0a155dc73e 100644 --- a/src/main/java/org/eclipsefoundation/marketplace/namespace/UrlParameterNames.java +++ b/src/main/java/org/eclipsefoundation/marketplace/namespace/UrlParameterNames.java @@ -27,6 +27,9 @@ public final class UrlParameterNames { public static final String TAGS = "tags"; public static final String MARKET_IDS = "market_ids"; public static final String ID = "id"; + public static final String LISTING_ID = "listing_id"; + public static final String READ = "read"; + public static final String FEATURE_ID = "feature_id"; private UrlParameterNames() { } diff --git a/src/main/java/org/eclipsefoundation/marketplace/resource/ErrorReportResource.java b/src/main/java/org/eclipsefoundation/marketplace/resource/ErrorReportResource.java new file mode 100644 index 0000000000000000000000000000000000000000..548d002d045f14a5adfd6472b65c518e87acff4e --- /dev/null +++ b/src/main/java/org/eclipsefoundation/marketplace/resource/ErrorReportResource.java @@ -0,0 +1,121 @@ +/* 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.ErrorReport; +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; + +/** + * Resource for retrieving {@linkplain ErrorReport} from the MongoDB instance. + * + * @author Martin Lowe + */ +@RequestScoped +@Path("/error") +@ResourceDataType(ErrorReport.class) +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class ErrorReportResource { + private static final Logger LOGGER = LoggerFactory.getLogger(ErrorReportResource.class); + + @Inject + MongoDao dao; + @Inject + CachingService<List<ErrorReport>> cachingService; + @Inject + RequestWrapper params; + @Inject + DtoFilter<ErrorReport> dtoFilter; + + /** + * Endpoint for /error/ to retrieve all ErrorReports from the database along with + * the given query string parameters. + * + * @param ErrorReportId int version of the ErrorReport ID + * @return response for the browser + */ + @GET + public Response select() { + MongoQuery<ErrorReport> q = new MongoQuery<>(params, dtoFilter, cachingService); + // retrieve the possible cached object + Optional<List<ErrorReport>> cachedResults = cachingService.get("all", params, + () -> StreamHelper.awaitCompletionStage(dao.get(q))); + if (!cachedResults.isPresent()) { + LOGGER.error("Error while retrieving cached ErrorReports"); + return Response.serverError().build(); + } + + // return the results as a response + return Response.ok(cachedResults.get()).build(); + } + + /** + * Endpoint for /error/ to post a new ErrorReport to the persistence layer. + * + * @param errorReport the ErrorReport object to insert into the database. + * @return response for the browser + */ + @POST + public Response postErrorReport(ErrorReport errorReport) { + MongoQuery<ErrorReport> q = new MongoQuery<>(params, dtoFilter, cachingService); + + // add the object, and await the result + StreamHelper.awaitCompletionStage(dao.add(q, Arrays.asList(errorReport))); + + // return the results as a response + return Response.ok().build(); + } + + /** + * Endpoint for /error/\<errorReportId\> to retrieve a specific ErrorReport from the + * database. + * + * @param errorReportId the ErrorReport ID + * @return response for the browser + */ + @GET + @Path("/{errorReportId}") + public Response select(@PathParam("errorReportId") String errorReportId) { + params.addParam(UrlParameterNames.ID, errorReportId); + + MongoQuery<ErrorReport> q = new MongoQuery<>(params, dtoFilter, cachingService); + // retrieve a cached version of the value for the current ErrorReport + Optional<List<ErrorReport>> cachedResults = cachingService.get(errorReportId, params, + () -> StreamHelper.awaitCompletionStage(dao.get(q))); + if (!cachedResults.isPresent()) { + LOGGER.error("Error while retrieving cached ErrorReport for ID {}", errorReportId); + return Response.serverError().build(); + } + + // return the results as a response + return Response.ok(cachedResults.get()).build(); + } +}