Skip to content
Snippets Groups Projects
Commit 4413c7b4 authored by Martin Lowe's avatar Martin Lowe :flag_ca:
Browse files

Create error report endpoint #22


Added error report endpoint w/ filter + codecs.

Change-Id: I5d884fa7bf7774b05dd9168372f0cd846bf9dadc
Signed-off-by: Martin Lowe's avatarMartin Lowe <martin.lowe@eclipse-foundation.org>
parent fe93d00a
No related branches found
No related tags found
1 merge request!29Create error report endpoint #22
Showing
with 517 additions and 13 deletions
/* 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;
}
}
/* 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());
}
}
...@@ -102,7 +102,7 @@ public class ListingCodec implements CollectibleCodec<Listing> { ...@@ -102,7 +102,7 @@ public class ListingCodec implements CollectibleCodec<Listing> {
public Listing decode(BsonReader reader, DecoderContext decoderContext) { public Listing decode(BsonReader reader, DecoderContext decoderContext) {
Document document = documentCodec.decode(reader, decoderContext); Document document = documentCodec.decode(reader, decoderContext);
Listing out = new Listing(); Listing out = new Listing();
// for each field, get the value from the encoded object and set it in POJO // for each field, get the value from the encoded object and set it in POJO
out.setId(document.getString(DatabaseFieldNames.DOCID)); out.setId(document.getString(DatabaseFieldNames.DOCID));
out.setName(document.getString(DatabaseFieldNames.NAME)); out.setName(document.getString(DatabaseFieldNames.NAME));
......
...@@ -12,14 +12,32 @@ import org.bson.conversions.Bson; ...@@ -12,14 +12,32 @@ import org.bson.conversions.Bson;
import org.eclipsefoundation.marketplace.model.RequestWrapper; import org.eclipsefoundation.marketplace.model.RequestWrapper;
/** /**
* @author martin * Filter interface for usage when querying data.
* *
* @author Martin Lowe
*/ */
public interface DtoFilter<T> { 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); 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); List<Bson> getAggregates(RequestWrapper wrap);
/**
* Returns the type of data this object will filter for.
*
* @return class of object to filter
*/
Class<T> getType(); Class<T> getType();
} }
/* 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;
}
}
/* 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;
}
}
...@@ -22,6 +22,7 @@ public final class DatabaseFieldNames { ...@@ -22,6 +22,7 @@ public final class DatabaseFieldNames {
public static final String URL = "url"; public static final String URL = "url";
public static final String NAME = "name"; public static final String NAME = "name";
public static final String OS = "os"; public static final String OS = "os";
public static final String LISTING_ID = "listing_id";
// listing fields // listing fields
public static final String LISTING_TEASER = "teaser"; public static final String LISTING_TEASER = "teaser";
...@@ -44,6 +45,7 @@ public final class DatabaseFieldNames { ...@@ -44,6 +45,7 @@ public final class DatabaseFieldNames {
public static final String LISTING_TAGS = "tags"; public static final String LISTING_TAGS = "tags";
public static final String CATEGORY_IDS = "category_ids"; public static final String CATEGORY_IDS = "category_ids";
public static final String LISTING_CATEGORIES = "categories"; 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 FEATURE_ID = "feature_id";
public static final String INSTALL_STATE = "install_state"; public static final String INSTALL_STATE = "install_state";
...@@ -58,13 +60,15 @@ public final class DatabaseFieldNames { ...@@ -58,13 +60,15 @@ public final class DatabaseFieldNames {
// category fields // category fields
public static final String MARKET_IDS = "market_ids"; public static final String MARKET_IDS = "market_ids";
public static final String INSTALL_JAVA_VERSION = "java_version"; // errorreport fields
public static final String INSTALL_VERSION = "version"; public static final String ERROR_TITLE = "title";
public static final String INSTALL_LISTING_ID = "listing_id"; public static final String ERROR_BODY = "body";
public static final String INSTALL_DATE = "date"; public static final String ERROR_DETAILED_MESSAGE = "detailed_message";
public static final String ECLIPSE_VERSION = "eclipse_version"; public static final String ERROR_READ = "read";
public static final String FEATURE_IDS = "feature_ids"; public static final String ERROR_FEATURE_IDS = "feature_ids";
public static final String LOCALE = "locale"; 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() { private DatabaseFieldNames() {
} }
......
...@@ -10,6 +10,7 @@ import org.eclipsefoundation.marketplace.dto.Catalog; ...@@ -10,6 +10,7 @@ import org.eclipsefoundation.marketplace.dto.Catalog;
import org.eclipsefoundation.marketplace.dto.Category; import org.eclipsefoundation.marketplace.dto.Category;
import org.eclipsefoundation.marketplace.dto.Listing; import org.eclipsefoundation.marketplace.dto.Listing;
import org.eclipsefoundation.marketplace.dto.Market; import org.eclipsefoundation.marketplace.dto.Market;
import org.eclipsefoundation.marketplace.dto.ErrorReport;
/** /**
* Mapping of DTO classes to their respective tables in the DB. * Mapping of DTO classes to their respective tables in the DB.
...@@ -20,7 +21,7 @@ import org.eclipsefoundation.marketplace.dto.Market; ...@@ -20,7 +21,7 @@ import org.eclipsefoundation.marketplace.dto.Market;
public enum DtoTableNames { public enum DtoTableNames {
LISTING(Listing.class, "listings"), LISTING(Listing.class, "listings"),
CATEGORY(Category.class, "categories"), 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 Class<?> baseClass;
private String tableName; private String tableName;
......
...@@ -27,6 +27,9 @@ public final class UrlParameterNames { ...@@ -27,6 +27,9 @@ public final class UrlParameterNames {
public static final String TAGS = "tags"; public static final String TAGS = "tags";
public static final String MARKET_IDS = "market_ids"; public static final String MARKET_IDS = "market_ids";
public static final String ID = "id"; 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() { private UrlParameterNames() {
} }
......
/* 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();
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment