diff --git a/pinery-service/pom.xml b/pinery-service/pom.xml index 41cf3103..a5ab06b2 100644 --- a/pinery-service/pom.xml +++ b/pinery-service/pom.xml @@ -34,7 +34,7 @@ io.prometheus - simpleclient + prometheus-metrics-core org.slf4j diff --git a/pinery-service/src/main/java/ca/on/oicr/pinery/service/impl/Cache.java b/pinery-service/src/main/java/ca/on/oicr/pinery/service/impl/Cache.java index 4bec529d..cc75ef3b 100644 --- a/pinery-service/src/main/java/ca/on/oicr/pinery/service/impl/Cache.java +++ b/pinery-service/src/main/java/ca/on/oicr/pinery/service/impl/Cache.java @@ -17,9 +17,12 @@ import ca.on.oicr.pinery.api.SampleProject; import ca.on.oicr.pinery.api.Type; import ca.on.oicr.pinery.api.User; -import io.prometheus.client.Gauge; -import io.prometheus.client.Histogram; +import io.prometheus.metrics.core.datapoints.Timer; +import io.prometheus.metrics.core.metrics.Gauge; +import io.prometheus.metrics.core.metrics.Histogram; import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.Date; import java.util.List; @@ -38,16 +41,16 @@ public class Cache implements DataProvider { private static final Logger log = LoggerFactory.getLogger(Cache.class); - private static final Histogram cacheUpdateTime = Histogram.build() + private static final Histogram cacheUpdateTime = Histogram.builder() .name("pinery_cache_update_time") .help("Time to update the cache (in seconds)") - .buckets(60, 180, 300, 600, 900, 1200, 1800, 3600) + .classicUpperBounds(60, 180, 300, 600, 900, 1200, 1800, 3600) .register(); - private static final Gauge cacheUpdateFailures = Gauge.build() + private static final Gauge cacheUpdateFailures = Gauge.builder() .name("pinery_cache_update_failures") .help("Number of consecutive cache update failures") .register(); - private static final Gauge cacheLastUpdated = Gauge.build() + private static final Gauge cacheLastUpdated = Gauge.builder() .name("pinery_cache_last_updated") .help("Timestamp of the last cache update (completion)") .register(); @@ -115,7 +118,7 @@ public void update() { } if (doUpdate) { - Histogram.Timer cacheUpdateTimer = cacheUpdateTime.startTimer(); + Timer cacheUpdateTimer = cacheUpdateTime.startTimer(); try { log.debug("Attempting update"); List newRequisitions = lims.getRequisitions(); @@ -157,7 +160,7 @@ public void update() { cacheComplete = true; lastUpdated = Instant.now(); cacheUpdateFailures.set(0); - cacheLastUpdated.setToCurrentTime(); + cacheLastUpdated.set(System.currentTimeMillis()); log.debug("Update successful"); } } catch (RuntimeException e) { diff --git a/pinery-ws/README.md b/pinery-ws/README.md index e09095a0..4c6bbf07 100644 --- a/pinery-ws/README.md +++ b/pinery-ws/README.md @@ -5,15 +5,14 @@ implementation such as the one by [MISO-LIMS](https://github.com/miso-lims/miso- ## Minimum Requirements -* Maven 3 -* JDK 11 +- Maven 3 +- JDK 17 +- Tomcat 10 ## Deploying to Tomcat ### Tomcat Configuration -Tomcat 9 is the recommended servlet container for Pinery. - You may wish to set the timezone explicitly, as not all source LIMS provide a time zone. It may also be necessary to increase the JVM memory. You can alter these by configuring Tomcat's JAVA_OPTS. e.g. @@ -33,6 +32,6 @@ JAVA_OPTS="-Duser.timezone=GMT -Djava.awt.headless=true -Xmx6144m -XX:MaxPermSiz 4. Copy the built `.war` file from the Pinery implemention to `$CATALINA_HOME/webapps/`, naming it to match your `context.xml` above. If Tomcat is configured to autodeploy, the webapp will be (re)deployed automatically; otherwise, deploy the webapp manually - * **WARNING**: In some cases with autodeploy enabled, Tomcat may delete the context XML during redeployment. + - **WARNING**: In some cases with autodeploy enabled, Tomcat may delete the context XML during redeployment. You can prevent this by stopping tomcat before copying the WAR into the webapps directory, or by making the file immutable via `chattr +i ` diff --git a/pinery-ws/pom.xml b/pinery-ws/pom.xml index d54887bb..76ab9668 100644 --- a/pinery-ws/pom.xml +++ b/pinery-ws/pom.xml @@ -31,20 +31,21 @@ pinery-ws-dto - io.springfox - springfox-swagger2 + org.springdoc + springdoc-openapi-starter-webmvc-ui + - io.springfox - springfox-swagger-ui + org.apache.commons + commons-lang3 org.springframework spring-webmvc - javax.servlet - javax.servlet-api + jakarta.servlet + jakarta.servlet-api junit @@ -97,15 +98,15 @@ io.prometheus - simpleclient + prometheus-metrics-core io.prometheus - simpleclient_hotspot + prometheus-metrics-instrumentation-jvm io.prometheus - simpleclient_servlet + prometheus-metrics-exporter-servlet-jakarta io.prometheus.jmx @@ -134,5 +135,16 @@ + + + org.apache.maven.plugins + maven-compiler-plugin + + + true + + + \ No newline at end of file diff --git a/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/AdminController.java b/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/AdminController.java index adac6d19..a290b2ce 100644 --- a/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/AdminController.java +++ b/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/AdminController.java @@ -2,10 +2,11 @@ import ca.on.oicr.pinery.service.impl.Cache; import ca.on.oicr.pinery.ws.component.RestException; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.PostMapping; @@ -13,17 +14,18 @@ import org.springframework.web.bind.annotation.RestController; @RestController -@Api(tags = {"Admin"}) +@Tag(name = "Admin") public class AdminController { - @Autowired private Cache cache; + @Autowired + private Cache cache; @PostMapping("/updatecache") - @ApiOperation(value = "Forces a cache refresh") + @Operation(summary = "Forces a cache refresh") @ApiResponses({ - @ApiResponse(code = 204, message = "Success"), - @ApiResponse(code = 400, message = "Caching not enabled"), - @ApiResponse(code = 500, message = "Error updating cache") + @ApiResponse(responseCode = "204", description = "Success"), + @ApiResponse(responseCode = "400", description = "Caching not enabled"), + @ApiResponse(responseCode = "500", description = "Error updating cache") }) @ResponseStatus(HttpStatus.NO_CONTENT) public void updateCache() { diff --git a/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/AssayResource.java b/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/AssayResource.java index 59d52eb8..346e1dcf 100644 --- a/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/AssayResource.java +++ b/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/AssayResource.java @@ -5,11 +5,13 @@ import ca.on.oicr.pinery.ws.component.RestException; import ca.on.oicr.ws.dto.AssayDto; import ca.on.oicr.ws.dto.Dtos; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; + import java.util.List; import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Autowired; @@ -22,23 +24,27 @@ @RestController @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE) -@Api(tags = {"Assays"}) +@Tag(name = "Assays") public class AssayResource { - @Autowired private AssayService assayService; + @Autowired + private AssayService assayService; @GetMapping("/assays") - @ApiOperation(value = "List all assays", response = AssayDto.class, responseContainer = "List") + @Operation(summary = "List all assays") public List getAssays() { List assays = assayService.getAssays(); return assays.stream().map(Dtos::asDto).collect(Collectors.toList()); } @GetMapping("/assay/{id}") - @ApiOperation(value = "Find assay by ID", response = AssayDto.class) - @ApiResponses({@ApiResponse(code = 404, message = "No assay found")}) + @Operation(summary = "Find assay by ID") + @ApiResponses({ + @ApiResponse(useReturnTypeSchema = true, responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "404", description = "No assay found", content = @Content) + }) public AssayDto getAssay( - @ApiParam(value = "ID of assay to fetch") @PathVariable("id") Integer id) { + @Parameter(description = "ID of assay to fetch") @PathVariable("id") Integer id) { Assay assay = assayService.getAssay(id); if (assay == null) { throw new RestException(HttpStatus.NOT_FOUND, String.format("No assay with ID: %d", id)); diff --git a/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/BoxResource.java b/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/BoxResource.java index 041c616a..1d706d03 100644 --- a/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/BoxResource.java +++ b/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/BoxResource.java @@ -4,8 +4,9 @@ import ca.on.oicr.pinery.service.BoxService; import ca.on.oicr.ws.dto.BoxDto; import ca.on.oicr.ws.dto.Dtos; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; @@ -15,16 +16,14 @@ @RestController @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE) -@Api(tags = {"Boxes"}) +@Tag(name = "Boxes") public class BoxResource { - @Autowired private BoxService boxService; + @Autowired + private BoxService boxService; @GetMapping("/boxes") - @ApiOperation( - value = "List all boxes", - response = ca.on.oicr.ws.dto.BoxDto.class, - responseContainer = "List") + @Operation(summary = "List all boxes") public List getBoxes() { List boxes = boxService.getBoxes(); return Dtos.asDtoList(boxes); diff --git a/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/InstrumentResource.java b/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/InstrumentResource.java index b77eeaf4..d0f1cecf 100644 --- a/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/InstrumentResource.java +++ b/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/InstrumentResource.java @@ -9,12 +9,14 @@ import ca.on.oicr.ws.dto.Dtos; import ca.on.oicr.ws.dto.InstrumentDto; import ca.on.oicr.ws.dto.InstrumentModelDto; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; + import com.google.common.collect.Lists; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; import java.net.URI; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; @@ -28,16 +30,14 @@ @RestController @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE) -@Api(tags = {"Instruments"}) +@Tag(name = "Instruments") public class InstrumentResource { - @Autowired private InstrumentService instrumentService; + @Autowired + private InstrumentService instrumentService; @GetMapping("/instrumentmodels") - @ApiOperation( - value = "List all instrument models", - response = ca.on.oicr.ws.dto.InstrumentModelDto.class, - responseContainer = "List") + @Operation(summary = "List all instrument models") public List getInstrumentModels(UriComponentsBuilder uriBuilder) { List instrumentModels = instrumentService.getInstrumentModels(); List result = Lists.newArrayList(); @@ -50,13 +50,14 @@ public List getInstrumentModels(UriComponentsBuilder uriBuil } @GetMapping("/instrumentmodel/{id}") - @ApiOperation( - value = "Find instrument model by ID", - response = ca.on.oicr.ws.dto.InstrumentModelDto.class) - @ApiResponses({@ApiResponse(code = 404, message = "Instrument model not found")}) + @Operation(summary = "Find instrument model by ID") + @ApiResponses({ + @ApiResponse(useReturnTypeSchema = true, responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "404", description = "Instrument model not found", content = @Content) + }) public InstrumentModelDto getInstrumentModel( UriComponentsBuilder uriBuilder, - @ApiParam(value = "ID of instrument model to fetch") @PathVariable("id") Integer id) { + @Parameter(description = "ID of instrument model to fetch") @PathVariable("id") Integer id) { InstrumentModel instrumentModel = instrumentService.getInstrumentModel(id); if (instrumentModel == null) { throw new RestException(HttpStatus.NOT_FOUND, "Instrument model not found"); @@ -67,10 +68,7 @@ public InstrumentModelDto getInstrumentModel( } @GetMapping("/instruments") - @ApiOperation( - value = "List all instruments", - response = ca.on.oicr.ws.dto.InstrumentDto.class, - responseContainer = "List") + @Operation(summary = "List all instruments") public List getInstruments(UriComponentsBuilder uriBuilder) { List instruments = instrumentService.getInstruments(); List result = Lists.newArrayList(); @@ -83,15 +81,14 @@ public List getInstruments(UriComponentsBuilder uriBuilder) { } @GetMapping("/instrumentmodel/{id}/instruments") - @ApiOperation( - value = "List all instruments for a given instrument model ID", - response = ca.on.oicr.ws.dto.InstrumentDto.class, - responseContainer = "List") - @ApiResponses({@ApiResponse(code = 404, message = "No instruments found")}) + @Operation(summary = "List all instruments for a given instrument model ID") + @ApiResponses({ + @ApiResponse(useReturnTypeSchema = true, responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "404", description = "No instruments found", content = @Content) + }) public List getInstrumentsModelInstrument( UriComponentsBuilder uriBuilder, - @ApiParam(value = "ID of instrument model to fetch instruments for") @PathVariable("id") - Integer id) { + @Parameter(description = "ID of instrument model to fetch instruments for") @PathVariable("id") Integer id) { List instruments = instrumentService.getInstrumentModelInstrument(id); List result = Lists.newArrayList(); for (Instrument instrument : instruments) { @@ -103,11 +100,14 @@ public List getInstrumentsModelInstrument( } @GetMapping("/instrument/{id}") - @ApiOperation(value = "Find instrument by ID", response = ca.on.oicr.ws.dto.InstrumentDto.class) - @ApiResponses({@ApiResponse(code = 404, message = "No instrument found")}) + @Operation(summary = "Find instrument by ID") + @ApiResponses({ + @ApiResponse(useReturnTypeSchema = true, responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "404", description = "No instrument found", content = @Content) + }) public InstrumentDto getInstrument( UriComponentsBuilder uriBuilder, - @ApiParam(value = "ID of instrument to fetch") @PathVariable("id") Integer instrumentId) { + @Parameter(description = "ID of instrument to fetch") @PathVariable("id") Integer instrumentId) { Instrument instrument = instrumentService.getInstrument(instrumentId); if (instrument == null) { throw new RestException(HttpStatus.NOT_FOUND, "No instrument found with ID: " + instrumentId); diff --git a/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/LaneProvenanceResource.java b/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/LaneProvenanceResource.java index 56b6cc9b..c09c755e 100644 --- a/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/LaneProvenanceResource.java +++ b/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/LaneProvenanceResource.java @@ -10,12 +10,15 @@ import ca.on.oicr.pinery.ws.util.VersionTransformer; import ca.on.oicr.ws.dto.Dtos; import ca.on.oicr.ws.dto.LaneProvenanceDto; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; + import com.google.common.annotations.VisibleForTesting; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; import java.util.Collections; import java.util.List; import java.util.Map; @@ -31,81 +34,74 @@ /** @author mlaszloffy */ @RestController @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE) -@Api(tags = {"Lane Provenance"}) +@Tag(name = "Lane Provenance") public class LaneProvenanceResource { - private static final VersionTransformer noopTransformer = - input -> input; + private static final VersionTransformer noopTransformer = input -> input; - private static final VersionTransformer v6Transformer = - input -> { - SimpleLaneProvenance modified = SimpleLaneProvenance.from(input); - modified.getLaneAttributes().remove(LimsLaneAttribute.RUN_PURPOSE.getKey()); - return modified; - }; + private static final VersionTransformer v6Transformer = input -> { + SimpleLaneProvenance modified = SimpleLaneProvenance.from(input); + modified.getLaneAttributes().remove(LimsLaneAttribute.RUN_PURPOSE.getKey()); + return modified; + }; - private static final VersionTransformer v3Transformer = - input -> { - SimpleLaneProvenance modified = v6Transformer.transform(input); - modified - .getSequencerRunAttributes() - .remove(LimsSequencerRunAttribute.CONTAINER_MODEL.getKey()); - modified - .getSequencerRunAttributes() - .remove(LimsSequencerRunAttribute.SEQUENCING_KIT.getKey()); - return modified; - }; + private static final VersionTransformer v3Transformer = input -> { + SimpleLaneProvenance modified = v6Transformer.transform(input); + modified + .getSequencerRunAttributes() + .remove(LimsSequencerRunAttribute.CONTAINER_MODEL.getKey()); + modified + .getSequencerRunAttributes() + .remove(LimsSequencerRunAttribute.SEQUENCING_KIT.getKey()); + return modified; + }; - private static final VersionTransformer v2Transformer = - input -> { - SimpleLaneProvenance modified = v3Transformer.transform(input); - modified - .getSequencerRunAttributes() - .remove(LimsSequencerRunAttribute.WORKFLOW_TYPE.getKey()); - return modified; - }; + private static final VersionTransformer v2Transformer = input -> { + SimpleLaneProvenance modified = v3Transformer.transform(input); + modified + .getSequencerRunAttributes() + .remove(LimsSequencerRunAttribute.WORKFLOW_TYPE.getKey()); + return modified; + }; - private static final VersionTransformer v1Transformer = - input -> { - SimpleLaneProvenance modified = v2Transformer.transform(input); - modified - .getSequencerRunAttributes() - .remove(LimsSequencerRunAttribute.SEQUENCING_PARAMETERS.getKey()); - modified.getLaneAttributes().remove(LimsLaneAttribute.QC_STATUS.getKey()); - modified.setSkip(false); - return modified; - }; + private static final VersionTransformer v1Transformer = input -> { + SimpleLaneProvenance modified = v2Transformer.transform(input); + modified + .getSequencerRunAttributes() + .remove(LimsSequencerRunAttribute.SEQUENCING_PARAMETERS.getKey()); + modified.getLaneAttributes().remove(LimsLaneAttribute.QC_STATUS.getKey()); + modified.setSkip(false); + return modified; + }; @VisibleForTesting - protected static final Map> - transformers = - new MapBuilder>() - .put("latest", noopTransformer) // - .put("v9", noopTransformer) // - .put("v8", noopTransformer) // - .put("v7", noopTransformer) // - .put("v6", v6Transformer) // - .put("v5", v6Transformer) // - .put("v4", v3Transformer) // - .put("v3", v3Transformer) // - .put("v2", v2Transformer) // - .put("v1", v1Transformer) // - .build(); + protected static final Map> transformers = new MapBuilder>() + .put("latest", noopTransformer) // + .put("v9", noopTransformer) // + .put("v8", noopTransformer) // + .put("v7", noopTransformer) // + .put("v6", v6Transformer) // + .put("v5", v6Transformer) // + .put("v4", v3Transformer) // + .put("v3", v3Transformer) // + .put("v2", v2Transformer) // + .put("v1", v1Transformer) // + .build(); private static final String versions = "latest, v9, v8, v7, v6, v5, v4, v3, v2, v1"; - @Autowired private LaneProvenanceService laneProvenanceService; + @Autowired + private LaneProvenanceService laneProvenanceService; @GetMapping(path = "/provenance/{version}/lane-provenance") - @ApiOperation( - value = "Get all lane provenance records", - response = LaneProvenanceDto.class, - responseContainer = "List") - @ApiResponses({@ApiResponse(code = 404, message = "Provenance version not found")}) + @Operation(summary = "Get all lane provenance records") + @ApiResponses({ + @ApiResponse(useReturnTypeSchema = true, responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "404", description = "Provenance version not found", content = @Content) + }) public List getLanes( - @ApiParam(allowableValues = versions) @PathVariable String version) { - VersionTransformer transformer = - transformers.get(version); + @Parameter(schema = @Schema(allowableValues = versions)) @PathVariable String version) { + VersionTransformer transformer = transformers.get(version); if (transformer == null) { throw new RestException( HttpStatus.NOT_FOUND, String.format("Provenance version '%s' not found", version)); @@ -118,7 +114,7 @@ public List getLanes( } @GetMapping("/lane-provenance") - @ApiOperation("Get version 1 of all lane provenance records") + @Operation(summary = "Get version 1 of all lane provenance records") @Deprecated public List getLanes() { return getLanes("v1"); diff --git a/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/OrderResource.java b/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/OrderResource.java index ba09daec..1d3c37ef 100644 --- a/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/OrderResource.java +++ b/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/OrderResource.java @@ -8,12 +8,14 @@ import ca.on.oicr.ws.dto.Dtos; import ca.on.oicr.ws.dto.OrderDto; import ca.on.oicr.ws.dto.OrderDtoSample; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; + import com.google.common.collect.Lists; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; import java.net.URI; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; @@ -27,17 +29,18 @@ @RestController @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE) -@Api(tags = {"Orders"}) +@Tag(name = "Orders") public class OrderResource { - @Autowired private OrderService orderService; + @Autowired + private OrderService orderService; void setOrderService(OrderService orderService) { this.orderService = orderService; } @GetMapping("/orders") - @ApiOperation(value = "List all orders", response = OrderDto.class, responseContainer = "List") + @Operation(summary = "List all orders") public List getOrders(UriComponentsBuilder uriBuilder) { List orders = orderService.getOrder(); List result = Lists.newArrayList(); @@ -50,11 +53,14 @@ public List getOrders(UriComponentsBuilder uriBuilder) { } @GetMapping("/order/{id}") - @ApiOperation(value = "Find order by ID", response = OrderDto.class) - @ApiResponses({@ApiResponse(code = 404, message = "No order found")}) + @Operation(summary = "Find order by ID") + @ApiResponses({ + @ApiResponse(useReturnTypeSchema = true, responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "404", description = "No order found", content = @Content) + }) public OrderDto getOrder( UriComponentsBuilder uriBuilder, - @ApiParam(value = "ID of order to fetch") @PathVariable("id") Integer id) { + @Parameter(description = "ID of order to fetch") @PathVariable("id") Integer id) { Order order = orderService.getOrder(id); if (order == null) { throw new RestException(HttpStatus.NOT_FOUND, "No order found with ID: " + id); diff --git a/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/RequisitionResource.java b/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/RequisitionResource.java index 656c7554..d6aad627 100644 --- a/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/RequisitionResource.java +++ b/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/RequisitionResource.java @@ -5,11 +5,13 @@ import ca.on.oicr.pinery.ws.component.RestException; import ca.on.oicr.ws.dto.Dtos; import ca.on.oicr.ws.dto.RequisitionDto; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; + import java.util.List; import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Autowired; @@ -23,26 +25,27 @@ @RestController @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE) -@Api(tags = {"Requisitions"}) +@Tag(name = "Requisitions") public class RequisitionResource { - @Autowired private RequisitionService requisitionService; + @Autowired + private RequisitionService requisitionService; @GetMapping("/requisitions") - @ApiOperation( - value = "List all requisitions", - response = RequisitionDto.class, - responseContainer = "List") + @Operation(summary = "List all requisitions") public List getRequisitions() { List requisitions = requisitionService.getRequisitions(); return requisitions.stream().map(Dtos::asDto).collect(Collectors.toList()); } @GetMapping("/requisition/{id}") - @ApiOperation(value = "Find requisition by ID", response = RequisitionDto.class) - @ApiResponses({@ApiResponse(code = 404, message = "No requisition found")}) + @Operation(summary = "Find requisition by ID") + @ApiResponses({ + @ApiResponse(useReturnTypeSchema = true, responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "404", description = "No requisition found", content = @Content) + }) public RequisitionDto getRequisition( - @ApiParam(value = "ID of requisition to fetch") @PathVariable("id") Integer id) { + @Parameter(description = "ID of requisition to fetch") @PathVariable("id") Integer id) { Requisition requisition = requisitionService.getRequisition(id); if (requisition == null) { throw new RestException( @@ -52,13 +55,14 @@ public RequisitionDto getRequisition( } @GetMapping("/requisition") - @ApiOperation(value = "Find requisition by name", response = RequisitionDto.class) + @Operation(summary = "Find requisition by name") @ApiResponses({ - @ApiResponse(code = 400, message = "Missing or invalid name parameter"), - @ApiResponse(code = 404, message = "No requisition found") + @ApiResponse(useReturnTypeSchema = true, responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "400", description = "Missing or invalid name parameter", content = @Content), + @ApiResponse(responseCode = "404", description = "No requisition found", content = @Content) }) public RequisitionDto getRequisitionByName( - @ApiParam(value = "Name of requisition to fetch") @RequestParam("name") String name) { + @Parameter(description = "Name of requisition to fetch") @RequestParam("name") String name) { if (name == null || name.isEmpty()) { throw new RestException(HttpStatus.BAD_REQUEST, "Name parameter is required"); } diff --git a/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/RunResource.java b/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/RunResource.java index f55b71b9..a8ba0cf4 100644 --- a/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/RunResource.java +++ b/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/RunResource.java @@ -9,12 +9,14 @@ import ca.on.oicr.ws.dto.RunDto; import ca.on.oicr.ws.dto.RunDtoPosition; import ca.on.oicr.ws.dto.RunDtoSample; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; + import com.google.common.collect.Lists; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; import java.net.URI; import java.util.List; import java.util.Set; @@ -30,24 +32,21 @@ @RestController @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE) -@Api(tags = {"Sequencer Runs"}) +@Tag(name = "Sequencer Runs") public class RunResource { - @Autowired private RunService runService; + @Autowired + private RunService runService; void setRunService(RunService runService) { this.runService = runService; } @GetMapping("/sequencerruns") - @ApiOperation( - value = "List all sequencer runs", - response = RunDto.class, - responseContainer = "List") + @Operation(summary = "List all sequencer runs") public List getRuns( UriComponentsBuilder uriBuilder, - @ApiParam(value = "filter by sampleId(s)") @RequestParam(name = "sampleId", required = false) - Set sampleIds) { + @Parameter(description = "filter by sampleId(s)") @RequestParam(name = "sampleId", required = false) Set sampleIds) { List runs = runService.getAll(sampleIds); List result = Lists.newArrayList(); for (Run run : runs) { @@ -59,11 +58,14 @@ public List getRuns( } @GetMapping("/sequencerrun/{id}") - @ApiOperation(value = "Find sequencer run by ID", response = RunDto.class) - @ApiResponses({@ApiResponse(code = 404, message = "No sequencer run found")}) + @Operation(summary = "Find sequencer run by ID") + @ApiResponses({ + @ApiResponse(useReturnTypeSchema = true, responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "404", description = "No sequencer run found", content = @Content) + }) public RunDto getRun( UriComponentsBuilder uriBuilder, - @ApiParam(value = "ID of sequencer run to fetch") @PathVariable("id") Integer id) { + @Parameter(description = "ID of sequencer run to fetch") @PathVariable("id") Integer id) { Run run = runService.getRun(id); if (run == null) { throw new RestException(HttpStatus.NOT_FOUND, "No run found with ID: " + id); @@ -75,14 +77,15 @@ public RunDto getRun( } @GetMapping("/sequencerrun") - @ApiOperation(value = "Find sequencer run by name", response = RunDto.class) + @Operation(summary = "Find sequencer run by name") @ApiResponses({ - @ApiResponse(code = 400, message = "Missing or invalid name parameter"), - @ApiResponse(code = 404, message = "No sequencer run found") + @ApiResponse(useReturnTypeSchema = true, responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "400", description = "Missing or invalid name parameter", content = @Content), + @ApiResponse(responseCode = "404", description = "No sequencer run found", content = @Content) }) public RunDto getRunByName( UriComponentsBuilder uriBuilder, - @ApiParam(value = "Name of sequencer run to fetch") @RequestParam("name") String runName) { + @Parameter(description = "Name of sequencer run to fetch") @RequestParam("name") String runName) { if (runName == null || runName.isEmpty()) { throw new RestException(HttpStatus.BAD_REQUEST, "Name parameter is required"); } diff --git a/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/SampleProvenanceResource.java b/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/SampleProvenanceResource.java index 5957a187..c1809e99 100644 --- a/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/SampleProvenanceResource.java +++ b/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/SampleProvenanceResource.java @@ -11,12 +11,15 @@ import ca.on.oicr.pinery.ws.util.VersionTransformer; import ca.on.oicr.ws.dto.Dtos; import ca.on.oicr.ws.dto.SampleProvenanceDto; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; + import com.google.common.annotations.VisibleForTesting; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; import java.util.Collections; import java.util.List; import java.util.Map; @@ -31,7 +34,7 @@ @RestController @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE) -@Api(tags = { "Sample Provenance" }) +@Tag(name = "Sample Provenance") public class SampleProvenanceResource { private static final VersionTransformer noopTransformer = input -> input; @@ -133,16 +136,19 @@ public class SampleProvenanceResource { private SampleProvenanceService sampleProvenanceService; @GetMapping("/provenance/versions") - @ApiOperation(value = "List available provenance versions", response = String.class, responseContainer = "List") + @Operation(summary = "List available provenance versions") public List getProvenanceVersions() { return transformers.keySet().stream().sorted().collect(Collectors.toList()); } @GetMapping("/provenance/{version}/sample-provenance") - @ApiOperation(value = "Get all sample provenance records", response = SampleProvenanceDto.class, responseContainer = "List") - @ApiResponses({ @ApiResponse(code = 404, message = "Provenance version not found") }) + @Operation(summary = "Get all sample provenance records") + @ApiResponses({ + @ApiResponse(useReturnTypeSchema = true, responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "404", description = "Provenance version not found", content = @Content) + }) public List getSamples( - @ApiParam(allowableValues = versions) @PathVariable String version) { + @Parameter(schema = @Schema(allowableValues = versions)) @PathVariable String version) { VersionTransformer transformer = transformers.get(version); if (transformer == null) { throw new RestException( @@ -156,7 +162,7 @@ public List getSamples( } @GetMapping("/sample-provenance") - @ApiOperation("Get version 1 of all sample provenance records") + @Operation(summary = "Get version 1 of all sample provenance records") @Deprecated public List getSamples() { return getSamples("v1"); diff --git a/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/SampleResource.java b/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/SampleResource.java index 144f096a..8c19066b 100644 --- a/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/SampleResource.java +++ b/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/SampleResource.java @@ -17,12 +17,14 @@ import ca.on.oicr.ws.dto.SampleProjectDto; import ca.on.oicr.ws.dto.SampleReferenceDto; import ca.on.oicr.ws.dto.TypeDto; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; + import com.google.common.collect.Lists; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; import java.net.URI; import java.time.ZonedDateTime; import java.util.Collections; @@ -41,31 +43,25 @@ @RestController @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE) -@Api(tags = {"Samples"}) +@Tag(name = "Samples") public class SampleResource { - @Autowired private SampleService sampleService; + @Autowired + private SampleService sampleService; @GetMapping(value = "/samples") - @ApiOperation(value = "List all samples", response = SampleDto.class, responseContainer = "List") - @ApiResponses({@ApiResponse(code = 400, message = "Invalid parameter")}) + @Operation(summary = "List all samples") + @ApiResponses({ + @ApiResponse(useReturnTypeSchema = true, responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "400", description = "Invalid parameter", content = @Content) + }) public List getSamples( UriComponentsBuilder uriBuilder, - @ApiParam(value = "filter by archived status") - @RequestParam(name = "archived", required = false) - Boolean archived, - @ApiParam(value = "filter by project(s)") @RequestParam(name = "project", required = false) - Set projects, - @ApiParam(value = "filter by sample type(s)") @RequestParam(name = "type", required = false) - Set types, - @ApiParam( - value = "filter to include samples created before this date", - example = "yyyy-mm-dd") - @RequestParam(name = "before", required = false) - String before, - @ApiParam(value = "filter to include samples created after this date", example = "yyyy-mm-dd") - @RequestParam(name = "after", required = false) - String after) { + @Parameter(description = "filter by archived status") @RequestParam(name = "archived", required = false) Boolean archived, + @Parameter(description = "filter by project(s)") @RequestParam(name = "project", required = false) Set projects, + @Parameter(description = "filter by sample type(s)") @RequestParam(name = "type", required = false) Set types, + @Parameter(description = "filter to include samples created before this date", example = "yyyy-mm-dd") @RequestParam(name = "before", required = false) String before, + @Parameter(description = "filter to include samples created after this date", example = "yyyy-mm-dd") @RequestParam(name = "after", required = false) String after) { ZonedDateTime beforeDateTime = null; ZonedDateTime afterDateTime = null; try { @@ -81,8 +77,7 @@ public List getSamples( "Invalid date format in parameter [before] or [after]. Use ISO8601 formatting. " + e.getMessage()); } - List samples = - sampleService.getSamples(archived, projects, types, beforeDateTime, afterDateTime); + List samples = sampleService.getSamples(archived, projects, types, beforeDateTime, afterDateTime); List result = Lists.newArrayList(); for (Sample sample : samples) { @@ -94,14 +89,15 @@ public List getSamples( } @GetMapping("/sample/{id}") - @ApiOperation(value = "Find sample by ID", response = SampleDto.class) + @Operation(summary = "Find sample by ID") @ApiResponses({ - @ApiResponse(code = 404, message = "No sample found"), - @ApiResponse(code = 400, message = "Invalid ID format") + @ApiResponse(useReturnTypeSchema = true, responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "404", description = "No sample found", content = @Content), + @ApiResponse(responseCode = "400", description = "Invalid ID format", content = @Content) }) public SampleDto getSample( UriComponentsBuilder uriBuilder, - @ApiParam(value = "ID of sample to fetch") @PathVariable("id") String id) { + @Parameter(description = "ID of sample to fetch") @PathVariable("id") String id) { Sample sample = null; try { sample = sampleService.getSample(id); @@ -117,10 +113,7 @@ public SampleDto getSample( } @GetMapping("/sample/projects") - @ApiOperation( - value = "List all projects", - response = SampleProjectDto.class, - responseContainer = "List") + @Operation(summary = "List all projects") public List getSampleProjects() { List projects = sampleService.getSampleProjects(); List result = Lists.newArrayList(); @@ -140,10 +133,7 @@ public int compare(SampleProjectDto o1, SampleProjectDto o2) { } @GetMapping("/sample/types") - @ApiOperation( - value = "List all sample types", - response = TypeDto.class, - responseContainer = "List") + @Operation(summary = "List all sample types") public List getTypes() { List types = sampleService.getTypes(); List result = Lists.newArrayList(); @@ -163,10 +153,7 @@ public int compare(TypeDto o1, TypeDto o2) { } @GetMapping("/sample/attributenames") - @ApiOperation( - value = "List all sample attribute names", - response = AttributeNameDto.class, - responseContainer = "List") + @Operation(summary = "List all sample attribute names") public List getAttributeNames() { List attributeNames = sampleService.getAttributeNames(); List result = Lists.newArrayList(); @@ -209,10 +196,7 @@ private void addUrls(Set relatedSamples, URI baseUri) { } @GetMapping("/sample/changelogs") - @ApiOperation( - value = "List changelogs for all samples", - response = ChangeLogDto.class, - responseContainer = "List") + @Operation(summary = "List changelogs for all samples") public List getChangeLogs(UriComponentsBuilder uriBuilder) { List changeLogs = sampleService.getChangeLogs(); List result = Lists.newArrayList(); @@ -228,11 +212,14 @@ public List getChangeLogs(UriComponentsBuilder uriBuilder) { } @GetMapping("/sample/{id}/changelog") - @ApiOperation(value = "Find sample changelog by sample ID", response = ChangeLogDto.class) - @ApiResponses({@ApiResponse(code = 404, message = "No sample changelog found")}) + @Operation(summary = "Find sample changelog by sample ID") + @ApiResponses({ + @ApiResponse(useReturnTypeSchema = true, responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "404", description = "No sample changelog found", content = @Content) + }) public ChangeLogDto getChangeLog( UriComponentsBuilder uriBuilder, - @ApiParam(value = "ID of sample to fetch changelogs for") @PathVariable("id") String id) { + @Parameter(description = "ID of sample to fetch changelogs for") @PathVariable("id") String id) { ChangeLog changeLog = null; try { changeLog = sampleService.getChangeLog(id); diff --git a/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/StatusController.java b/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/StatusController.java index 99e7df35..0c8442d7 100644 --- a/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/StatusController.java +++ b/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/StatusController.java @@ -7,21 +7,22 @@ import ca.on.oicr.gsi.status.ServerConfig; import ca.on.oicr.gsi.status.StatusPage; import ca.on.oicr.pinery.service.impl.Cache; +import io.swagger.v3.oas.annotations.Hidden; + import java.io.IOException; import java.io.OutputStream; import java.util.stream.Stream; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletResponse; import javax.xml.stream.XMLStreamException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; -import springfox.documentation.annotations.ApiIgnore; @Controller @RequestMapping("/") -@ApiIgnore +@Hidden public class StatusController { public static final ServerConfig SERVER_CONFIG = new ServerConfig() { diff --git a/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/UserResource.java b/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/UserResource.java index cae57bc6..5e82d988 100644 --- a/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/UserResource.java +++ b/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/UserResource.java @@ -7,12 +7,14 @@ import ca.on.oicr.pinery.ws.component.RestException; import ca.on.oicr.ws.dto.Dtos; import ca.on.oicr.ws.dto.UserDto; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; + import com.google.common.collect.Lists; -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import io.swagger.annotations.ApiResponse; -import io.swagger.annotations.ApiResponses; import java.net.URI; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; @@ -26,13 +28,14 @@ @RestController @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE) -@Api(tags = {"Users"}) +@Tag(name = "Users") public class UserResource { - @Autowired private UserService userService; + @Autowired + private UserService userService; @GetMapping("/users") - @ApiOperation(value = "List all users", response = UserDto.class, responseContainer = "List") + @Operation(summary = "List all users") public List getUsers(UriComponentsBuilder uriBuilder) { List users = userService.getUsers(); List result = Lists.newArrayList(); @@ -45,11 +48,14 @@ public List getUsers(UriComponentsBuilder uriBuilder) { } @GetMapping("/user/{id}") - @ApiOperation(value = "Find user by ID", response = UserDto.class) - @ApiResponses({@ApiResponse(code = 404, message = "No user found")}) + @Operation(summary = "Find user by ID") + @ApiResponses({ + @ApiResponse(useReturnTypeSchema = true, responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "404", description = "No user found", content = @Content) + }) public UserDto getUser( UriComponentsBuilder uriBuilder, - @ApiParam(value = "ID of user to fetch") @PathVariable("id") Integer id) { + @Parameter(description = "ID of user to fetch") @PathVariable("id") Integer id) { User user = userService.getUser(id); if (user == null) { throw new RestException(HttpStatus.NOT_FOUND, "No user found with ID: " + id); diff --git a/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/component/DefaultExceptionHandler.java b/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/component/DefaultExceptionHandler.java index eec1a628..ae198b2a 100644 --- a/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/component/DefaultExceptionHandler.java +++ b/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/component/DefaultExceptionHandler.java @@ -6,6 +6,7 @@ import org.slf4j.LoggerFactory; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -47,14 +48,27 @@ protected ResponseEntity handleExceptionInternal( Exception exception, Object body, HttpHeaders headers, - HttpStatus status, + HttpStatusCode status, WebRequest request) { logError(exception, status); - RestError error = new RestError(status, exception.getLocalizedMessage()); + RestError error = null; + if (status instanceof HttpStatus httpStatus) { + error = new RestError(httpStatus, exception.getLocalizedMessage()); + } else { + String reasonPhrase = null; + if (status.is4xxClientError()) { + reasonPhrase = "Client Error"; + } else if (status.is5xxServerError()) { + reasonPhrase = "Server Error"; + } else { + reasonPhrase = "Unknown Error"; + } + error = new RestError(status.value(), reasonPhrase, exception.getLocalizedMessage()); + } return new ResponseEntity<>(error, headers, status); } - private static void logError(Throwable exception, HttpStatus status) { + private static void logError(Throwable exception, HttpStatusCode status) { if (status.is5xxServerError()) { log.error("Error handling REST request", exception); } else { diff --git a/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/component/PineryContextListener.java b/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/component/PineryContextListener.java index e5296bf9..71d6b1fb 100644 --- a/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/component/PineryContextListener.java +++ b/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/component/PineryContextListener.java @@ -1,26 +1,24 @@ package ca.on.oicr.pinery.ws.component; -import io.prometheus.client.hotspot.DefaultExports; import io.prometheus.jmx.JmxCollector; +import io.prometheus.metrics.instrumentation.jvm.JvmMetrics; + import java.io.File; import java.io.IOException; import java.net.URL; import javax.management.MalformedObjectNameException; -import javax.servlet.ServletContextEvent; -import javax.servlet.ServletContextListener; -public class PineryContextListener implements ServletContextListener { +import jakarta.servlet.ServletContextEvent; +import jakarta.servlet.ServletContextListener; - @Override - public void contextDestroyed(ServletContextEvent event) {} +public class PineryContextListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent event) { // Export all JVM HotSpot stats to Prometheus - DefaultExports.initialize(); + JvmMetrics.builder().register(); try { - URL yamlConfig = - Thread.currentThread().getContextClassLoader().getResource("tomcat-prometheus.yml"); + URL yamlConfig = Thread.currentThread().getContextClassLoader().getResource("tomcat-prometheus.yml"); if (yamlConfig == null) { throw new IllegalStateException("Prometheus configuration not found"); } @@ -30,4 +28,5 @@ public void contextInitialized(ServletContextEvent event) { throw new IllegalStateException("Failed to load Prometheus configuration.", e); } } -} + +} \ No newline at end of file diff --git a/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/component/RestError.java b/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/component/RestError.java index 8f19265a..d361c43e 100644 --- a/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/component/RestError.java +++ b/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/component/RestError.java @@ -29,6 +29,12 @@ public RestError(HttpStatus status, String detail) { this.detail = detail; } + public RestError(int status, String reasonPhrase, String detail) { + this.status = status; + this.message = reasonPhrase; + this.detail = detail; + } + /** @return the HTTP status code associated with this error */ public int getStatus() { return status; diff --git a/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/component/SwaggerConfig.java b/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/component/SwaggerConfig.java index 75c772c7..cb5c2e97 100644 --- a/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/component/SwaggerConfig.java +++ b/pinery-ws/src/main/java/ca/on/oicr/pinery/ws/component/SwaggerConfig.java @@ -1,22 +1,36 @@ package ca.on.oicr.pinery.ws.component; -import com.google.common.collect.ImmutableList; -import java.util.Collections; +import org.springdoc.core.configuration.SpringDocConfiguration; +import org.springdoc.core.configuration.SpringDocSpecPropertiesConfiguration; +import org.springdoc.core.configuration.SpringDocUIConfiguration; +import org.springdoc.core.models.GroupedOpenApi; +import org.springdoc.core.properties.SpringDocConfigProperties; +import org.springdoc.core.properties.SwaggerUiConfigProperties; +import org.springdoc.core.properties.SwaggerUiOAuthProperties; +import org.springdoc.webmvc.core.configuration.MultipleOpenApiSupportConfiguration; +import org.springdoc.webmvc.core.configuration.SpringDocWebMvcConfiguration; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.web.bind.annotation.RequestMethod; -import springfox.documentation.builders.ApiInfoBuilder; -import springfox.documentation.builders.PathSelectors; -import springfox.documentation.builders.RequestHandlerSelectors; -import springfox.documentation.builders.ResponseMessageBuilder; -import springfox.documentation.service.ApiInfo; -import springfox.documentation.spi.DocumentationType; -import springfox.documentation.spring.web.plugins.Docket; -import springfox.documentation.swagger2.annotations.EnableSwagger2; +import org.springframework.context.annotation.Import; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; @Configuration -@EnableSwagger2 +@EnableWebMvc +@Import({ + SpringDocConfiguration.class, + SpringDocConfigProperties.class, + SpringDocSpecPropertiesConfiguration.class, + SpringDocWebMvcConfiguration.class, + MultipleOpenApiSupportConfiguration.class, + org.springdoc.webmvc.ui.SwaggerConfig.class, + SwaggerUiConfigProperties.class, + SwaggerUiOAuthProperties.class, + SpringDocUIConfiguration.class +}) public class SwaggerConfig { @Value("${project.name}") @@ -26,20 +40,17 @@ public class SwaggerConfig { String projectVersion; @Bean - public Docket api() { - return new Docket(DocumentationType.SWAGGER_2) - .select() - .apis(RequestHandlerSelectors.any()) - .paths(PathSelectors.any()) - .build() - .apiInfo(metaData()) - .globalResponseMessage( - RequestMethod.GET, - ImmutableList.of(new ResponseMessageBuilder().code(200).message("OK").build())) - .globalResponseMessage(RequestMethod.POST, Collections.emptyList()); + public GroupedOpenApi api() { + return GroupedOpenApi.builder() + .group("API") + .packagesToScan("ca.on.oicr.pinery") + .build(); } - private ApiInfo metaData() { - return new ApiInfoBuilder().title(projectName).version(projectVersion).build(); + @Bean + public OpenAPI openApi() { + return new OpenAPI() + .info(new Info().title(projectName).version(projectVersion)); } -} + +} \ No newline at end of file diff --git a/pinery-ws/src/main/webapp/WEB-INF/spring-servlet.xml b/pinery-ws/src/main/webapp/WEB-INF/spring-servlet.xml index 3fa12c9f..25c83701 100644 --- a/pinery-ws/src/main/webapp/WEB-INF/spring-servlet.xml +++ b/pinery-ws/src/main/webapp/WEB-INF/spring-servlet.xml @@ -22,6 +22,7 @@ + @@ -44,12 +45,6 @@ - - - - - diff --git a/pinery-ws/src/main/webapp/WEB-INF/web.xml b/pinery-ws/src/main/webapp/WEB-INF/web.xml index 0d9e3845..df65a47f 100644 --- a/pinery-ws/src/main/webapp/WEB-INF/web.xml +++ b/pinery-ws/src/main/webapp/WEB-INF/web.xml @@ -24,7 +24,7 @@ metrics - io.prometheus.client.exporter.MetricsServlet + io.prometheus.metrics.exporter.servlet.jakarta.PrometheusMetricsServlet 1 diff --git a/pinery-ws/src/test/resources/test-spring-servlet.xml b/pinery-ws/src/test/resources/test-spring-servlet.xml index f90b3ef1..ed083588 100644 --- a/pinery-ws/src/test/resources/test-spring-servlet.xml +++ b/pinery-ws/src/test/resources/test-spring-servlet.xml @@ -20,6 +20,7 @@ + diff --git a/pom.xml b/pom.xml index 284c4c21..1f084853 100644 --- a/pom.xml +++ b/pom.xml @@ -18,12 +18,8 @@ 1.4.0 5.3.27 3.14.0.Final - 1.0.1 - 3.0.0 4.13.1 1.3 - 2.6 - 3.4 9.1-901.jdbc3 2.17.1 @@ -36,15 +32,13 @@ 1.9.4 2.19.1 2.5 - 0.0.21 - 0.9 + 1.3.1 2.3 2.5 2.10 2.19.1 2.19.1 - 2.2 1.9.1 @@ -129,71 +123,23 @@ com.fasterxml.jackson jackson-bom - 2.13.2.20220328 + 2.17.2 pom import org.springframework - spring-core - ${spring.version} - - - org.springframework - spring-web - ${spring.version} - - - org.springframework - spring-beans - ${spring.version} - - - org.springframework - spring-context - ${spring.version} - - - org.springframework - spring-aop - ${spring.version} - - - org.springframework - spring-context-support - ${spring.version} - - - org.springframework - spring-tx - ${spring.version} - - - org.springframework - spring-orm - ${spring.version} - - - org.springframework - spring-jdbc - ${spring.version} - - - org.springframework - spring-test - ${spring.version} - test - - - org.springframework - spring-webmvc - ${spring.version} + spring-framework-bom + 6.1.13 + pom + import - javax.servlet - javax.servlet-api - 3.1.0 + jakarta.servlet + jakarta.servlet-api + 6.0.0 + provided org.jboss.resteasy @@ -215,25 +161,26 @@ csvjdbc ${csvjdbc.version} + io.prometheus - simpleclient + prometheus-metrics-core ${prometheus.version} io.prometheus - simpleclient_hotspot + prometheus-metrics-instrumentation-jvm ${prometheus.version} io.prometheus - simpleclient_servlet + prometheus-metrics-exporter-servlet-jakarta ${prometheus.version} io.prometheus.jmx collector - ${prometheus.jmx.version} + 1.0.1 @@ -263,12 +210,12 @@ commons-lang commons-lang - ${commons-lang.version} + 2.6 org.apache.commons commons-lang3 - ${commons-lang3.version} + 3.17.0 @@ -285,17 +232,12 @@ ca.on.oicr.gsi server-utils - ${server-utils.version} + 1.0.1 - io.springfox - springfox-swagger2 - ${springfox.version} - - - io.springfox - springfox-swagger-ui - ${springfox.version} + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.6.0 @@ -359,11 +301,6 @@ - - org.apache.tomcat.maven - tomcat7-maven-plugin - ${tomcat7-maven-plugin.version} - org.codehaus.mojo build-helper-maven-plugin @@ -375,7 +312,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.13.0 17 true