Skip to content

Commit

Permalink
Add ApplicationErrorResource (#23)
Browse files Browse the repository at this point in the history
* Add ApplicationErrorResource JAX-RS resource
* Add a factory method to ApplicationError.Resolved to create an
 instance from a boolean
* Add ApplicationErrorPage model that represents a "page" of errors
* Add dropwizard-core dependency (with exclusions for dependency
 convergence errors)
* Make dropwizard-jdbi3 dependency provided scope

Fixes #20
  • Loading branch information
sleberknight authored Oct 28, 2020
1 parent 38821a4 commit 7807c08
Show file tree
Hide file tree
Showing 9 changed files with 496 additions and 3 deletions.
73 changes: 73 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
<jakarta.annotation-api.version>1.3.5</jakarta.annotation-api.version>
<jakarta.xml.bind-api.version>2.3.3</jakarta.xml.bind-api.version>
<javassist.version>3.27.0-GA</javassist.version>
<joda-time.version>2.10.7</joda-time.version>
<kiwi.version>0.13.0</kiwi.version>

<!-- Versions for provided dependencies -->
Expand Down Expand Up @@ -75,10 +76,51 @@
<version>${kiwi.version}</version>
</dependency>

<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-core</artifactId>
<version>${dropwizard.version}</version>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
<exclusion>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
<exclusion>
<groupId>jakarta.activation</groupId>
<artifactId>jakarta.activation-api</artifactId>
</exclusion>
<exclusion>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
</exclusion>
<exclusion>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
</exclusion>
<exclusion>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
</exclusion>
<exclusion>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-jdbi3</artifactId>
<version>${dropwizard.version}</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>com.github.ben-manes.caffeine</groupId>
Expand Down Expand Up @@ -165,6 +207,12 @@
<version>${javassist.version}</version>
</dependency>

<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>${joda-time.version}</version>
</dependency>

<!-- provided dependencies -->

<dependency>
Expand Down Expand Up @@ -210,6 +258,31 @@

<!-- test dependencies -->

<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-testing</artifactId>
<version>${dropwizard.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
<exclusion>
<groupId>jakarta.activation</groupId>
<artifactId>jakarta.activation-api</artifactId>
</exclusion>
<exclusion>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
</exclusion>
<exclusion>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>io.zonky.test</groupId>
<artifactId>embedded-postgres</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ public enum Resolved {
Resolved(boolean value) {
this.value = value;
}

public static Resolved of(boolean value) {
return value ? YES : NO;
}

public boolean toBoolean() {
return value;
}
}

@With
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.kiwiproject.dropwizard.error.model;

import lombok.Builder;
import lombok.Getter;
import lombok.ToString;
import org.kiwiproject.search.PaginatedResult;

import java.util.List;

/**
* Represents a "page" of {@link ApplicationError} results, e.g. when using pagination.
*
* @implNote This class is not intended to be compared using equals or hashCode.
*/
@Builder
@ToString(exclude = "items")
public class ApplicationErrorPage implements PaginatedResult {

@Getter
private final List<ApplicationError> items;

@Getter
private final long totalCount;

@Getter
private final int pageNumber;

@Getter
private final int pageSize;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package org.kiwiproject.dropwizard.error.resource;

import static javax.ws.rs.core.MediaType.APPLICATION_JSON;

import com.codahale.metrics.annotation.ExceptionMetered;
import com.codahale.metrics.annotation.Timed;
import org.kiwiproject.dropwizard.error.dao.ApplicationErrorDao;
import org.kiwiproject.dropwizard.error.dao.ApplicationErrorStatus;
import org.kiwiproject.dropwizard.error.model.ApplicationError;
import org.kiwiproject.dropwizard.error.model.ApplicationErrorPage;
import org.kiwiproject.jaxrs.KiwiStandardResponses;

import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.OptionalLong;

/**
* JAX-RS resource class for retrieving application errors, as well as marking them resolved.
*/
@Path("/kiwi/application-errors")
@Produces(APPLICATION_JSON)
public class ApplicationErrorResource {

private final ApplicationErrorDao errorDao;

public ApplicationErrorResource(ApplicationErrorDao errorDao) {
this.errorDao = errorDao;
}

/**
* GET endpoint to retrieve an application error by ID.
*
* @param id the ID as a path parameter
* @return the Response
*/
@GET
@Path("/{id}")
@Timed
@ExceptionMetered
public Response getById(@PathParam("id") OptionalLong id) {
Optional<ApplicationError> errorOptional = errorDao.getById(id.orElseThrow());
return KiwiStandardResponses.standardGetResponse("id", id, errorOptional, ApplicationError.class);
}

/**
* GET endpoint to paginate application errors.
*
* @param statusParam status query parameter indicating which application errors to include
* @param pageNumber the page number query parameter, starting from one
* @param pageSize the page size query parameter
* @return the Response
* @see ApplicationErrorStatus
*/
@GET
@Timed
@ExceptionMetered
public Response getErrors(@QueryParam("status") @DefaultValue("UNRESOLVED") String statusParam,
@QueryParam("pageNumber") @DefaultValue("1") OptionalInt pageNumber,
@QueryParam("pageSize") @DefaultValue("25") OptionalInt pageSize) {

var status = ApplicationErrorStatus.from(statusParam);
var thePageNumber = pageNumber.orElseThrow();
var thePageSize = pageSize.orElseThrow();
var errors = errorDao.getErrors(status, thePageNumber, thePageSize);
var count = errorDao.count(status);

var appErrors = ApplicationErrorPage.builder()
.pageNumber(thePageNumber)
.pageSize(thePageSize)
.totalCount(count)
.items(errors)
.build();

return Response.ok(appErrors).build();
}

/**
* Resolve an application error by ID.
*
* @param id the ID path parameter
* @return the Response
*/
@PUT
@Path("/resolve/{id}")
@Timed
@ExceptionMetered
public Response resolve(@PathParam("id") OptionalLong id) {
var resolvedError = errorDao.resolve(id.orElseThrow());
return KiwiStandardResponses.standardPutResponse(resolvedError);
}

/**
* Resolve <em>all</em> unresolved application errors.
*
* @return the Response
*/
@PUT
@Path("/resolve")
@Timed
@ExceptionMetered
public Response resolveAllUnresolved() {
var count = errorDao.resolveAllUnresolvedErrors();
var entity = Map.of("resolvedCount", count);
return KiwiStandardResponses.standardPutResponse(entity);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ private ApplicationError newApplicationErrorWithUpdatedDate(ZonedDateTime dateTi
return ApplicationError.builder()
.createdAt(dateTime)
.updatedAt(dateTime)
.resolved(resolved == Resolved.YES)
.resolved(resolved.toBoolean())
.description("test error at " + dateTime)
.hostName(hostName)
.ipAddress(ipAddress)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
import org.jdbi.v3.core.h2.H2DatabasePlugin;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.kiwiproject.dropwizard.error.dao.ApplicationErrorJdbc;
import org.kiwiproject.test.junit.jupiter.Jdbi3DaoExtension;

@DisplayName("Jdbi3ApplicationErrorDao (H2)")
public class H2Jdbi3ApplicationErrorDaoTest extends AbstractJdbi3ApplicationErrorDaoTest {

private static JdbcDataSource DATA_SOURCE;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
package org.kiwiproject.dropwizard.error.dao.jdbi3;

import org.jdbi.v3.core.h2.H2DatabasePlugin;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.kiwiproject.test.junit.jupiter.Jdbi3DaoExtension;
import org.kiwiproject.test.junit.jupiter.PostgresLiquibaseTestExtension;

@DisplayName("Jdbi3ApplicationErrorDao (Postgres)")
public class PostgresJdbi3ApplicationErrorDaoTest extends AbstractJdbi3ApplicationErrorDaoTest {

@RegisterExtension
static final PostgresLiquibaseTestExtension POSTGRES =
new PostgresLiquibaseTestExtension("dropwizard-app-errors-migrations.xml");

@RegisterExtension
final Jdbi3DaoExtension<Jdbi3ApplicationErrorDao> jdbi3DaoExtension =
@RegisterExtension final Jdbi3DaoExtension<Jdbi3ApplicationErrorDao> jdbi3DaoExtension =
Jdbi3DaoExtension.<Jdbi3ApplicationErrorDao>builder()
.daoType(Jdbi3ApplicationErrorDao.class)
.dataSource(POSTGRES.getTestDataSource())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,21 @@ private void setupThrowable() {
throwable = new UncheckedIOException("File not found or something", cause);
}

@Nested
class ResolvedEnum {

@ParameterizedTest
@CsvSource({
"true, YES",
"false, NO"
})
void shouldCreateInstanceFromBoolean(boolean value, Resolved expected) {
assertThat(Resolved.of(value))
.describedAs("Expected %s for value %b", expected, value)
.isEqualTo(expected);
}
}

@Nested
class HostInformation {

Expand Down
Loading

0 comments on commit 7807c08

Please sign in to comment.