diff --git a/common/exceptions/README.md b/common/exceptions/README.md new file mode 100644 index 00000000..3237bfe1 --- /dev/null +++ b/common/exceptions/README.md @@ -0,0 +1,9 @@ +# Shared Exceptions Code for the Rest API + +This package contains shared code that is used to handle exceptions +from REST requests to mojo frames and back. + + +## Building + +This is a standard Java gradle project and is build as usual by `./gradlew build`. diff --git a/common/exceptions/build.gradle b/common/exceptions/build.gradle new file mode 100644 index 00000000..735739da --- /dev/null +++ b/common/exceptions/build.gradle @@ -0,0 +1,9 @@ +apply from: project(":").file('gradle/java.gradle') + +dependencies { + implementation group: 'org.slf4j', name: 'slf4j-api' +} + +test { + useJUnitPlatform() +} diff --git a/common/exceptions/src/main/java/ai/h2o/mojos/deploy/common/exceptions/ScoringException.java b/common/exceptions/src/main/java/ai/h2o/mojos/deploy/common/exceptions/ScoringException.java new file mode 100644 index 00000000..d5514078 --- /dev/null +++ b/common/exceptions/src/main/java/ai/h2o/mojos/deploy/common/exceptions/ScoringException.java @@ -0,0 +1,12 @@ +package ai.h2o.mojos.deploy.common.exceptions; + +public class ScoringException extends RuntimeException { + + public ScoringException(String message) { + super(message); + } + + public ScoringException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/common/exceptions/src/main/java/ai/h2o/mojos/deploy/common/exceptions/ScoringShapleyException.java b/common/exceptions/src/main/java/ai/h2o/mojos/deploy/common/exceptions/ScoringShapleyException.java new file mode 100644 index 00000000..197b0753 --- /dev/null +++ b/common/exceptions/src/main/java/ai/h2o/mojos/deploy/common/exceptions/ScoringShapleyException.java @@ -0,0 +1,8 @@ +package ai.h2o.mojos.deploy.common.exceptions; + +public class ScoringShapleyException extends RuntimeException { + + public ScoringShapleyException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/common/transform/src/main/java/ai/h2o/mojos/deploy/common/transform/MojoScorer.java b/common/transform/src/main/java/ai/h2o/mojos/deploy/common/transform/MojoScorer.java index 30fe9456..16fd965c 100644 --- a/common/transform/src/main/java/ai/h2o/mojos/deploy/common/transform/MojoScorer.java +++ b/common/transform/src/main/java/ai/h2o/mojos/deploy/common/transform/MojoScorer.java @@ -133,6 +133,7 @@ public ScoreResponse score(ScoreRequest request) { } catch (Exception e) { log.info("Failed shapley contribution due to: {}", e.getMessage()); log.debug(" - failure cause: ", e); + return response; } return response; } diff --git a/local-rest-scorer/build.gradle b/local-rest-scorer/build.gradle index 58a5fab6..7396b761 100644 --- a/local-rest-scorer/build.gradle +++ b/local-rest-scorer/build.gradle @@ -5,6 +5,7 @@ plugins { apply from: project(":").file('gradle/java.gradle') dependencies { + implementation project(':common:exceptions') implementation project(':common:rest-spring-api') implementation project(':common:transform') implementation group: 'ai.h2o', name: 'h2o-genmodel' diff --git a/local-rest-scorer/src/main/java/ai/h2o/mojos/deploy/local/rest/controller/ModelsApiController.java b/local-rest-scorer/src/main/java/ai/h2o/mojos/deploy/local/rest/controller/ModelsApiController.java index 00e4c811..08273261 100644 --- a/local-rest-scorer/src/main/java/ai/h2o/mojos/deploy/local/rest/controller/ModelsApiController.java +++ b/local-rest-scorer/src/main/java/ai/h2o/mojos/deploy/local/rest/controller/ModelsApiController.java @@ -1,5 +1,7 @@ package ai.h2o.mojos.deploy.local.rest.controller; +import ai.h2o.mojos.deploy.common.exceptions.ScoringException; +import ai.h2o.mojos.deploy.common.exceptions.ScoringShapleyException; import ai.h2o.mojos.deploy.common.rest.api.ModelApi; import ai.h2o.mojos.deploy.common.rest.model.CapabilityType; import ai.h2o.mojos.deploy.common.rest.model.ContributionRequest; @@ -18,7 +20,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; @@ -72,29 +73,28 @@ public ResponseEntity getScore(ScoreRequest request) { ScoreResponse scoreResponse = scorer.score(request); return ResponseEntity.ok(scoreResponse); } catch (Exception e) { - log.info("Failed scoring request: {}, due to: {}", request, e.getMessage()); - log.debug(" - failure cause: ", e); - return ResponseEntity.badRequest().build(); + String message = String.format( + "Failed scoring request: %s, due to: %s", request, e.getMessage()); + throw new ScoringException(message, e); } } @Override public ResponseEntity getScoreByFile(String file) { if (Strings.isNullOrEmpty(file)) { - log.info("Request is missing a valid CSV file path"); - return ResponseEntity.badRequest().build(); + throw new ScoringException("Request is missing a valid CSV file path"); } try { log.info("Got scoring request for CSV"); return ResponseEntity.ok(scorer.scoreCsv(file)); } catch (IOException e) { - log.info("Failed loading CSV file: {}, due to: {}", file, e.getMessage()); - log.debug(" - failure cause: ", e); - return ResponseEntity.badRequest().build(); + String message = String.format( + "Failed loading CSV file: %s, due to: %s", file, e.getMessage()); + throw new ScoringException(message, e); } catch (Exception e) { - log.info("Failed scoring CSV file: {}, due to: {}", file, e.getMessage()); - log.debug(" - failure cause: ", e); - return ResponseEntity.badRequest().build(); + String message = String.format( + "Failed scoring CSV file: %s, due to: %s", file, e.getMessage()); + throw new ScoringException(message, e); } } @@ -106,13 +106,15 @@ public ResponseEntity getContribution( ContributionResponse contributionResponse = scorer.computeContribution(request); return ResponseEntity.ok(contributionResponse); - } catch (UnsupportedOperationException e) { - log.info("Unsupported operation due to: {}", e.getMessage()); - return ResponseEntity.status(HttpStatus.NOT_IMPLEMENTED).build(); + } catch (IllegalArgumentException e) { + String message = String.format("Unsupported operation due to: %s", e.getMessage()); + throw new ScoringShapleyException(message, e); } catch (Exception e) { - log.info("Failed shapley contribution request due to: {}", e.getMessage()); - log.debug(" - failure cause: ", e); - return ResponseEntity.badRequest().build(); + String message = String.format( + "Failed shapley contribution request: %s, due to: %s", + request, + e.getMessage()); + throw new ScoringShapleyException(message, e); } } diff --git a/local-rest-scorer/src/main/java/ai/h2o/mojos/deploy/local/rest/exception/ModelsApiExceptionController.java b/local-rest-scorer/src/main/java/ai/h2o/mojos/deploy/local/rest/exception/ModelsApiExceptionController.java new file mode 100644 index 00000000..c71e8668 --- /dev/null +++ b/local-rest-scorer/src/main/java/ai/h2o/mojos/deploy/local/rest/exception/ModelsApiExceptionController.java @@ -0,0 +1,33 @@ +package ai.h2o.mojos.deploy.local.rest.exception; + +import ai.h2o.mojos.deploy.common.exceptions.ScoringException; +import ai.h2o.mojos.deploy.common.exceptions.ScoringShapleyException; +import ai.h2o.mojos.deploy.local.rest.controller.ModelsApiController; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +@ControllerAdvice +public class ModelsApiExceptionController extends ResponseEntityExceptionHandler { + + private static final Logger log = LoggerFactory.getLogger(ModelsApiController.class); + + @ExceptionHandler(value = ScoringException.class) + protected ResponseEntity handleScoringException(ScoringException scoringException) { + log.info(scoringException.getMessage()); + log.debug(" - failure cause: ", scoringException); + return new ResponseEntity<>(scoringException.getMessage(), HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(value = ScoringShapleyException.class) + protected ResponseEntity handleScoringShapleyException( + ScoringShapleyException scoringShapleyException) { + log.info(scoringShapleyException.getMessage()); + log.debug(" - failure cause: ", scoringShapleyException); + return new ResponseEntity<>(scoringShapleyException.getMessage(), HttpStatus.BAD_REQUEST); + } +} diff --git a/settings.gradle b/settings.gradle index 825f8aba..7f148a86 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,6 +1,7 @@ rootProject.name = 'dai-deployment-templates' include 'aws-lambda-scorer:lambda-template' include 'aws-lambda-scorer:terraform-recipe' +include 'common:exceptions' include 'common:jdbc' include 'common:rest-java-model' include 'common:rest-jdbc-spring-api'