From 4e1040d065714ade3a41389056a18b0c169c6583 Mon Sep 17 00:00:00 2001 From: Max Schweikart Date: Sun, 30 Jul 2023 14:30:03 +0200 Subject: [PATCH 01/22] refactor: define /solve/... router --- build.gradle | 4 +- .../kit/provideq/toolbox/api/SolveRouter.java | 46 +++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java diff --git a/build.gradle b/build.gradle index 64d3e7ad..3fbf5545 100644 --- a/build.gradle +++ b/build.gradle @@ -14,9 +14,9 @@ repositories { } dependencies { - implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-webflux' implementation 'org.springframework.boot:spring-boot-starter-validation' - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.4' + implementation 'org.springdoc:springdoc-openapi-starter-webflux-ui:2.0.4' implementation 'com.bpodgursky:jbool_expressions:1.24' implementation files('lib/de.ovgu.featureide.lib.fm-v3.9.1.jar', 'lib/uvl-parser.jar') testImplementation 'org.springframework.boot:spring-boot-starter-test' diff --git a/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java b/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java new file mode 100644 index 00000000..34b6308d --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java @@ -0,0 +1,46 @@ +package edu.kit.provideq.toolbox.api; + +import edu.kit.provideq.toolbox.featuremodel.anomaly.MetaSolverFeatureModelAnomaly; +import edu.kit.provideq.toolbox.maxcut.MetaSolverMaxCut; +import edu.kit.provideq.toolbox.meta.MetaSolver; +import edu.kit.provideq.toolbox.sat.MetaSolverSat; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.reactive.config.EnableWebFlux; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.ServerResponse; + +import java.util.Set; + +import static org.springdoc.webflux.core.fn.SpringdocRouteBuilder.route; +import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.web.reactive.function.server.RequestPredicates.accept; +import static org.springframework.web.reactive.function.server.ServerResponse.ok; + +@Configuration +@EnableWebFlux +public class SolveRouter { + private final Set> metaSolvers; + + public SolveRouter(MetaSolverSat sat, MetaSolverMaxCut maxCut, MetaSolverFeatureModelAnomaly featureModelAnomaly) { + this.metaSolvers = Set.of(sat, maxCut, featureModelAnomaly); + } + + @Bean + RouterFunction getRoutes() { + return metaSolvers.stream() + .map(this::defineRouteForMetaSolver) + .reduce(RouterFunction::and) + .orElseThrow(); // we should always have at least one route or the toolbox is useless + } + + private RouterFunction defineRouteForMetaSolver(MetaSolver metaSolver) { + String problemId = metaSolver.getClass().getSimpleName(); // FIXME + return route().POST( + "/solve/" + problemId, + accept(APPLICATION_JSON), + req -> ok().build(), + ops -> ops.operationId("solve-" + problemId) + ).build(); + } +} From bc402631627fdbb7420f52197ca8507ce294f81f Mon Sep 17 00:00:00 2001 From: Max Schweikart Date: Sun, 30 Jul 2023 15:15:38 +0200 Subject: [PATCH 02/22] feat: configure generic /solve API docs --- .../kit/provideq/toolbox/api/SolveRouter.java | 33 ++++++++++++++++--- .../MetaSolverFeatureModelAnomaly.java | 3 +- .../toolbox/maxcut/MetaSolverMaxCut.java | 3 +- .../kit/provideq/toolbox/meta/MetaSolver.java | 11 +++++-- .../provideq/toolbox/meta/ProblemType.java | 27 +++++++++++++-- .../provideq/toolbox/sat/MetaSolverSat.java | 3 +- 6 files changed, 68 insertions(+), 12 deletions(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java b/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java index 34b6308d..b26b74dd 100644 --- a/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java +++ b/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java @@ -1,19 +1,29 @@ package edu.kit.provideq.toolbox.api; +import edu.kit.provideq.toolbox.Solution; +import edu.kit.provideq.toolbox.SolutionHandle; import edu.kit.provideq.toolbox.featuremodel.anomaly.MetaSolverFeatureModelAnomaly; import edu.kit.provideq.toolbox.maxcut.MetaSolverMaxCut; import edu.kit.provideq.toolbox.meta.MetaSolver; import edu.kit.provideq.toolbox.sat.MetaSolverSat; +import org.springdoc.core.fn.builders.requestbody.Builder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; import org.springframework.web.reactive.config.EnableWebFlux; import org.springframework.web.reactive.function.server.RouterFunction; import org.springframework.web.reactive.function.server.ServerResponse; +import reactor.core.publisher.Mono; import java.util.Set; +import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder; +import static org.springdoc.core.fn.builders.content.Builder.contentBuilder; +import static org.springdoc.core.fn.builders.requestbody.Builder.requestBodyBuilder; +import static org.springdoc.core.fn.builders.schema.Builder.schemaBuilder; import static org.springdoc.webflux.core.fn.SpringdocRouteBuilder.route; import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; import static org.springframework.web.reactive.function.server.RequestPredicates.accept; import static org.springframework.web.reactive.function.server.ServerResponse.ok; @@ -35,12 +45,27 @@ RouterFunction getRoutes() { } private RouterFunction defineRouteForMetaSolver(MetaSolver metaSolver) { - String problemId = metaSolver.getClass().getSimpleName(); // FIXME + String problemId = metaSolver.getProblemType().getId(); return route().POST( - "/solve/" + problemId, + "/solve2/" + problemId, accept(APPLICATION_JSON), - req -> ok().build(), - ops -> ops.operationId("solve-" + problemId) + req -> handleRouteForMetaSolver(metaSolver), + ops -> ops + .operationId("/solve/" + problemId) + .tag(problemId) + .requestBody(requestBodyBuilder() + .content(contentBuilder() + .schema(schemaBuilder().implementation(metaSolver.getProblemType().getRequestType())) + .mediaType(APPLICATION_JSON_VALUE) + ) + .required(true) + ) + .response(responseBuilder() + .responseCode("200").implementation(SolutionHandle.class) + ) ).build(); } + private Mono handleRouteForMetaSolver(MetaSolver metaSolver) { + return ok().build(); + } } diff --git a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/MetaSolverFeatureModelAnomaly.java b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/MetaSolverFeatureModelAnomaly.java index 58e35cab..be75dc1c 100644 --- a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/MetaSolverFeatureModelAnomaly.java +++ b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/MetaSolverFeatureModelAnomaly.java @@ -3,6 +3,7 @@ import edu.kit.provideq.toolbox.featuremodel.anomaly.solvers.FeatureModelAnomalySolver; import edu.kit.provideq.toolbox.meta.MetaSolver; import edu.kit.provideq.toolbox.meta.Problem; +import edu.kit.provideq.toolbox.meta.ProblemType; import edu.kit.provideq.toolbox.meta.setting.MetaSolverSetting; import java.util.ArrayList; import java.util.List; @@ -21,7 +22,7 @@ public class MetaSolverFeatureModelAnomaly extends MetaSolver< @Autowired public MetaSolverFeatureModelAnomaly(FeatureModelAnomalySolver anomalySolver) { - super(anomalySolver); + super(ProblemType.FEATURE_MODEL_ANOMALY, anomalySolver); } @Override diff --git a/src/main/java/edu/kit/provideq/toolbox/maxcut/MetaSolverMaxCut.java b/src/main/java/edu/kit/provideq/toolbox/maxcut/MetaSolverMaxCut.java index 309b8f03..d2c0a97d 100644 --- a/src/main/java/edu/kit/provideq/toolbox/maxcut/MetaSolverMaxCut.java +++ b/src/main/java/edu/kit/provideq/toolbox/maxcut/MetaSolverMaxCut.java @@ -5,6 +5,7 @@ import edu.kit.provideq.toolbox.maxcut.solvers.QiskitMaxCutSolver; import edu.kit.provideq.toolbox.meta.MetaSolver; import edu.kit.provideq.toolbox.meta.Problem; +import edu.kit.provideq.toolbox.meta.ProblemType; import edu.kit.provideq.toolbox.meta.setting.MetaSolverSetting; import java.util.ArrayList; import java.util.List; @@ -21,7 +22,7 @@ public class MetaSolverMaxCut extends MetaSolver { @Autowired public MetaSolverMaxCut(QiskitMaxCutSolver qiskitMaxCutSolver, GamsMaxCutSolver gamsMaxCutSolver) { - super(qiskitMaxCutSolver, gamsMaxCutSolver); + super(ProblemType.MAX_CUT, qiskitMaxCutSolver, gamsMaxCutSolver); } @Override diff --git a/src/main/java/edu/kit/provideq/toolbox/meta/MetaSolver.java b/src/main/java/edu/kit/provideq/toolbox/meta/MetaSolver.java index 807e66b9..4b409ce3 100644 --- a/src/main/java/edu/kit/provideq/toolbox/meta/MetaSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/meta/MetaSolver.java @@ -20,17 +20,20 @@ public abstract class MetaSolver< SolverT extends ProblemSolver> { protected Set solvers = new HashSet<>(); + private ProblemType problemType; public MetaSolver() { } - public MetaSolver(List problemSolvers) { + public MetaSolver(ProblemType problemType, List problemSolvers) { solvers.addAll(problemSolvers); + this.problemType = problemType; } @SafeVarargs - public MetaSolver(SolverT... problemSolvers) { + public MetaSolver(ProblemType problemType, SolverT... problemSolvers) { solvers.addAll(List.of(problemSolvers)); + this.problemType = problemType; } /** @@ -90,4 +93,8 @@ public List getSettings() { public int hashCode() { return this.solvers.hashCode(); } + + public ProblemType getProblemType() { + return problemType; + } } diff --git a/src/main/java/edu/kit/provideq/toolbox/meta/ProblemType.java b/src/main/java/edu/kit/provideq/toolbox/meta/ProblemType.java index 7c0ba8e1..6f2aac8f 100644 --- a/src/main/java/edu/kit/provideq/toolbox/meta/ProblemType.java +++ b/src/main/java/edu/kit/provideq/toolbox/meta/ProblemType.java @@ -1,5 +1,9 @@ package edu.kit.provideq.toolbox.meta; +import edu.kit.provideq.toolbox.featuremodel.SolveFeatureModelRequest; +import edu.kit.provideq.toolbox.maxcut.SolveMaxCutRequest; +import edu.kit.provideq.toolbox.sat.SolveSatRequest; + /** * The type of problem to solve. */ @@ -8,14 +12,14 @@ public enum ProblemType { * A satisfiability problem: * For a given boolean formula, check if there is an interpretation that satisfies the formula. */ - SAT, + SAT("sat", SolveSatRequest.class), /** * An optimization problem: * For a given graph, find the optimal separation of vertices that maximises the cut crossing edge * weight sum. */ - MAX_CUT, + MAX_CUT("max-cut", SolveMaxCutRequest.class), /** * A searching problem: @@ -23,5 +27,22 @@ public enum ProblemType { * False-Optional Features, Redundant Constraints. * More information */ - FEATURE_MODEL_ANOMALY, + FEATURE_MODEL_ANOMALY("feature-model-anomaly", SolveFeatureModelRequest.class), + ; + + private final String id; + private final Class requestType; + + ProblemType(String id, Class requestType) { + this.id = id; + this.requestType = requestType; + } + + public String getId() { + return id; + } + + public Class getRequestType() { + return requestType; + } } diff --git a/src/main/java/edu/kit/provideq/toolbox/sat/MetaSolverSat.java b/src/main/java/edu/kit/provideq/toolbox/sat/MetaSolverSat.java index ecfa85c5..9cdfa142 100644 --- a/src/main/java/edu/kit/provideq/toolbox/sat/MetaSolverSat.java +++ b/src/main/java/edu/kit/provideq/toolbox/sat/MetaSolverSat.java @@ -3,6 +3,7 @@ import edu.kit.provideq.toolbox.format.cnf.dimacs.DimacsCnfSolution; import edu.kit.provideq.toolbox.meta.MetaSolver; import edu.kit.provideq.toolbox.meta.Problem; +import edu.kit.provideq.toolbox.meta.ProblemType; import edu.kit.provideq.toolbox.meta.setting.MetaSolverSetting; import edu.kit.provideq.toolbox.sat.solvers.GamsSatSolver; import edu.kit.provideq.toolbox.sat.solvers.SatSolver; @@ -20,7 +21,7 @@ public class MetaSolverSat extends MetaSolver Date: Sun, 30 Jul 2023 16:50:09 +0200 Subject: [PATCH 03/22] refactor: move solving implementation to SolveRouter --- a.out | 0 .../provideq/toolbox/ProblemController.java | 33 --------- .../kit/provideq/toolbox/SubRoutinePool.java | 5 +- .../kit/provideq/toolbox/api/SolveRouter.java | 68 +++++++++++++++++-- .../FeatureModelAnomalyController.java | 4 +- .../toolbox/maxcut/MaxCutController.java | 6 -- .../kit/provideq/toolbox/meta/MetaSolver.java | 12 ++++ .../provideq/toolbox/meta/ProblemType.java | 7 +- .../provideq/toolbox/sat/SatController.java | 6 -- 9 files changed, 82 insertions(+), 59 deletions(-) create mode 100644 a.out diff --git a/a.out b/a.out new file mode 100644 index 00000000..e69de29b diff --git a/src/main/java/edu/kit/provideq/toolbox/ProblemController.java b/src/main/java/edu/kit/provideq/toolbox/ProblemController.java index d172af0c..49611262 100644 --- a/src/main/java/edu/kit/provideq/toolbox/ProblemController.java +++ b/src/main/java/edu/kit/provideq/toolbox/ProblemController.java @@ -1,7 +1,6 @@ package edu.kit.provideq.toolbox; import edu.kit.provideq.toolbox.meta.MetaSolver; -import edu.kit.provideq.toolbox.meta.Problem; import edu.kit.provideq.toolbox.meta.ProblemSolver; import edu.kit.provideq.toolbox.meta.ProblemType; import edu.kit.provideq.toolbox.meta.SubRoutineDefinition; @@ -9,8 +8,6 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.RestController; @@ -27,42 +24,12 @@ @RestController public abstract class ProblemController> { - private ApplicationContext context; private final SolutionManager solutionManager = new SolutionManager<>(); public abstract ProblemType getProblemType(); public abstract MetaSolver getMetaSolver(); - @Autowired - public void setApplicationContext(ApplicationContext context) { - this.context = context; - } - - public Solution solve(SolveRequest request) { - Solution solution = solutionManager.createSolution(); - Problem problem = new Problem<>(request.requestContent, getProblemType()); - - SolverT solver = getMetaSolver() - .getSolver(request.requestedSolverId) - .orElseGet(() -> getMetaSolver().findSolver(problem, request.requestedMetaSolverSettings)); - - solution.setSolverName(solver.getName()); - - SubRoutinePool subRoutinePool = - request.requestedSubSolveRequests == null - ? context.getBean(SubRoutinePool.class) - : context.getBean(SubRoutinePool.class, request.requestedSubSolveRequests); - - long start = System.currentTimeMillis(); - solver.solve(problem, solution, subRoutinePool); - long finish = System.currentTimeMillis(); - - solution.setExecutionMilliseconds(finish - start); - - return solution; - } - public Solution findSolution(long id) { var solution = solutionManager.getSolution(id); if (solution == null) { diff --git a/src/main/java/edu/kit/provideq/toolbox/SubRoutinePool.java b/src/main/java/edu/kit/provideq/toolbox/SubRoutinePool.java index 061ab955..bfbae979 100644 --- a/src/main/java/edu/kit/provideq/toolbox/SubRoutinePool.java +++ b/src/main/java/edu/kit/provideq/toolbox/SubRoutinePool.java @@ -45,7 +45,8 @@ public void setProblemControllerProvider(ProblemControllerProvider problemContro */ public Function> getSubRoutine( ProblemType problemType) { - return content -> { + return null; // FIXME + /*return content -> { SolveRequest subRoutine = subRoutineCalls.get(problemType); if (subRoutine == null) { subRoutine = new SolveRequest<>(); @@ -63,6 +64,6 @@ public Function> getSubRouti ? extends ProblemSolver>) problemControllerProvider.getProblemController(problemType); return problemController.solve(newSolveRequest); - }; + };*/ } } diff --git a/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java b/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java index b26b74dd..f7e243a8 100644 --- a/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java +++ b/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java @@ -2,19 +2,31 @@ import edu.kit.provideq.toolbox.Solution; import edu.kit.provideq.toolbox.SolutionHandle; +import edu.kit.provideq.toolbox.SolveRequest; +import edu.kit.provideq.toolbox.SubRoutinePool; import edu.kit.provideq.toolbox.featuremodel.anomaly.MetaSolverFeatureModelAnomaly; import edu.kit.provideq.toolbox.maxcut.MetaSolverMaxCut; import edu.kit.provideq.toolbox.meta.MetaSolver; +import edu.kit.provideq.toolbox.meta.Problem; +import edu.kit.provideq.toolbox.meta.ProblemSolver; import edu.kit.provideq.toolbox.sat.MetaSolverSat; -import org.springdoc.core.fn.builders.requestbody.Builder; +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.http.MediaType; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.validation.BeanPropertyBindingResult; +import org.springframework.validation.Errors; +import org.springframework.validation.Validator; import org.springframework.web.reactive.config.EnableWebFlux; import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.reactive.function.server.ServerResponse; +import org.springframework.web.server.ServerWebInputException; import reactor.core.publisher.Mono; +import java.util.Objects; import java.util.Set; import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder; @@ -31,9 +43,13 @@ @EnableWebFlux public class SolveRouter { private final Set> metaSolvers; + private final ApplicationContext context; + private final Validator validator; - public SolveRouter(MetaSolverSat sat, MetaSolverMaxCut maxCut, MetaSolverFeatureModelAnomaly featureModelAnomaly) { + public SolveRouter(MetaSolverSat sat, MetaSolverMaxCut maxCut, MetaSolverFeatureModelAnomaly featureModelAnomaly, ApplicationContext context, Validator validator) { this.metaSolvers = Set.of(sat, maxCut, featureModelAnomaly); + this.context = context; + this.validator = validator; } @Bean @@ -47,9 +63,9 @@ RouterFunction getRoutes() { private RouterFunction defineRouteForMetaSolver(MetaSolver metaSolver) { String problemId = metaSolver.getProblemType().getId(); return route().POST( - "/solve2/" + problemId, + "/solve/" + problemId, accept(APPLICATION_JSON), - req -> handleRouteForMetaSolver(metaSolver), + req -> handleRouteForMetaSolver(metaSolver, req), ops -> ops .operationId("/solve/" + problemId) .tag(problemId) @@ -65,7 +81,45 @@ private RouterFunction defineRouteForMetaSolver(MetaSolver handleRouteForMetaSolver(MetaSolver metaSolver) { - return ok().build(); + private Mono handleRouteForMetaSolver(MetaSolver metaSolver, ServerRequest req) { + var x = req + .bodyToMono(new ParameterizedTypeReference>() {}) + .doOnNext(this::validate) + .map(request -> solve(metaSolver, request)) + .map(Solution::toStringSolution); + return ok().body(x, new ParameterizedTypeReference<>() {}); + } + + private void validate(SolveRequest request) { + Errors errors = new BeanPropertyBindingResult(request, "request"); + validator.validate(request, errors); + if (errors.hasErrors()) { + throw new ServerWebInputException(errors.toString()); + } + } + + private > Solution + solve(MetaSolver metaSolver, SolveRequest request) { + Solution solution = metaSolver.getSolutionManager().createSolution(); + Problem problem = new Problem<>(request.requestContent, metaSolver.getProblemType()); + + SolverT solver = metaSolver + .getSolver(request.requestedSolverId) + .orElseGet(() -> metaSolver.findSolver(problem, request.requestedMetaSolverSettings)); + + solution.setSolverName(solver.getName()); + + SubRoutinePool subRoutinePool = + request.requestedSubSolveRequests == null + ? context.getBean(SubRoutinePool.class) + : context.getBean(SubRoutinePool.class, request.requestedSubSolveRequests); + + long start = System.currentTimeMillis(); + solver.solve(problem, solution, subRoutinePool); + long finish = System.currentTimeMillis(); + + solution.setExecutionMilliseconds(finish - start); + + return solution; } } diff --git a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/FeatureModelAnomalyController.java b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/FeatureModelAnomalyController.java index 5a2b5cb7..937d0529 100644 --- a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/FeatureModelAnomalyController.java +++ b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/FeatureModelAnomalyController.java @@ -46,7 +46,7 @@ public MetaSolver return metaSolver; } - @CrossOrigin + /*@CrossOrigin @PostMapping("/solve/feature-model/anomaly/void") public SolutionHandle findVoidFeatureModel(@RequestBody @Valid SolveFeatureModelRequest request) { return solveAnomaly(request, FeatureModelAnomaly.VOID); @@ -78,7 +78,7 @@ private SolutionHandle solveAnomaly(SolveFeatureModelRequest request, request.replaceContent(new FeatureModelAnomalyProblem(request.requestContent, anomaly))); solution.setSolverName(solution.getSolverName() + ": " + anomaly.name); return solution; - } + }*/ @CrossOrigin @GetMapping("/solve/feature-model/anomaly/") diff --git a/src/main/java/edu/kit/provideq/toolbox/maxcut/MaxCutController.java b/src/main/java/edu/kit/provideq/toolbox/maxcut/MaxCutController.java index af1a987b..1a17e6fe 100644 --- a/src/main/java/edu/kit/provideq/toolbox/maxcut/MaxCutController.java +++ b/src/main/java/edu/kit/provideq/toolbox/maxcut/MaxCutController.java @@ -37,12 +37,6 @@ public MetaSolver getMetaSolver() { return metaSolver; } - @CrossOrigin - @PostMapping("/solve/max-cut") - public SolutionHandle solveMaxCut(@RequestBody @Valid SolveMaxCutRequest request) { - return super.solve(request); - } - @CrossOrigin @GetMapping("/solve/max-cut") public SolutionHandle getSolution(@RequestParam(name = "id") long id) { diff --git a/src/main/java/edu/kit/provideq/toolbox/meta/MetaSolver.java b/src/main/java/edu/kit/provideq/toolbox/meta/MetaSolver.java index 4b409ce3..4e91f412 100644 --- a/src/main/java/edu/kit/provideq/toolbox/meta/MetaSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/meta/MetaSolver.java @@ -1,6 +1,12 @@ package edu.kit.provideq.toolbox.meta; +import edu.kit.provideq.toolbox.SolutionManager; +import edu.kit.provideq.toolbox.SolveRequest; import edu.kit.provideq.toolbox.meta.setting.MetaSolverSetting; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.web.reactive.function.server.ServerRequest; +import reactor.core.publisher.Mono; + import java.util.HashSet; import java.util.List; import java.util.Optional; @@ -19,6 +25,8 @@ public abstract class MetaSolver< SolutionT, SolverT extends ProblemSolver> { + private final SolutionManager solutionManager = new SolutionManager<>(); + protected Set solvers = new HashSet<>(); private ProblemType problemType; @@ -97,4 +105,8 @@ public int hashCode() { public ProblemType getProblemType() { return problemType; } + + public SolutionManager getSolutionManager() { + return solutionManager; + } } diff --git a/src/main/java/edu/kit/provideq/toolbox/meta/ProblemType.java b/src/main/java/edu/kit/provideq/toolbox/meta/ProblemType.java index 6f2aac8f..b7a149d7 100644 --- a/src/main/java/edu/kit/provideq/toolbox/meta/ProblemType.java +++ b/src/main/java/edu/kit/provideq/toolbox/meta/ProblemType.java @@ -1,5 +1,6 @@ package edu.kit.provideq.toolbox.meta; +import edu.kit.provideq.toolbox.SolveRequest; import edu.kit.provideq.toolbox.featuremodel.SolveFeatureModelRequest; import edu.kit.provideq.toolbox.maxcut.SolveMaxCutRequest; import edu.kit.provideq.toolbox.sat.SolveSatRequest; @@ -31,9 +32,9 @@ public enum ProblemType { ; private final String id; - private final Class requestType; + private final Class> requestType; - ProblemType(String id, Class requestType) { + ProblemType(String id, Class> requestType) { this.id = id; this.requestType = requestType; } @@ -42,7 +43,7 @@ public String getId() { return id; } - public Class getRequestType() { + public Class> getRequestType() { return requestType; } } diff --git a/src/main/java/edu/kit/provideq/toolbox/sat/SatController.java b/src/main/java/edu/kit/provideq/toolbox/sat/SatController.java index 228eb606..6e454400 100644 --- a/src/main/java/edu/kit/provideq/toolbox/sat/SatController.java +++ b/src/main/java/edu/kit/provideq/toolbox/sat/SatController.java @@ -38,12 +38,6 @@ public MetaSolver getMetaSolver() { return metaSolver; } - @CrossOrigin - @PostMapping("/solve/sat") - public SolutionHandle solveSat(@RequestBody @Valid SolveSatRequest request) { - return super.solve(request).toStringSolution(DimacsCnfSolution::toHumanReadableString); - } - @CrossOrigin @GetMapping("/solve/sat") public SolutionHandle getSolution(@RequestParam(name = "id") long id) { From c77b0b9bb0728c4f872c9003c84edf36311de2b6 Mon Sep 17 00:00:00 2001 From: Max Schweikart Date: Sun, 30 Jul 2023 19:54:05 +0200 Subject: [PATCH 04/22] refactor: move /sub-routines/... routes to functional router --- .../provideq/toolbox/ProblemController.java | 21 -------- .../kit/provideq/toolbox/api/SolveRouter.java | 53 ++++++++++++++++--- .../FeatureModelAnomalyController.java | 19 ++----- .../toolbox/maxcut/MaxCutController.java | 6 --- .../kit/provideq/toolbox/meta/MetaSolver.java | 8 +-- .../provideq/toolbox/sat/SatController.java | 6 --- 6 files changed, 56 insertions(+), 57 deletions(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/ProblemController.java b/src/main/java/edu/kit/provideq/toolbox/ProblemController.java index 49611262..afdc5790 100644 --- a/src/main/java/edu/kit/provideq/toolbox/ProblemController.java +++ b/src/main/java/edu/kit/provideq/toolbox/ProblemController.java @@ -5,7 +5,6 @@ import edu.kit.provideq.toolbox.meta.ProblemType; import edu.kit.provideq.toolbox.meta.SubRoutineDefinition; import java.util.List; -import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import org.springframework.http.HttpStatus; @@ -40,21 +39,6 @@ public Solution findSolution(long id) { return solution; } - public SolverT getSolver(String id) { - Optional solver = getMetaSolver() - .getAllSolvers() - .stream() - .filter(s -> id.equals(s.getId())) - .findFirst(); - - if (solver.isEmpty()) { - throw new ResponseStatusException(HttpStatus.NOT_FOUND, - String.format("Unable to find solver %s", id)); - } - - return solver.get(); - } - public Set getSolvers() { return getMetaSolver() .getAllSolvers() @@ -62,9 +46,4 @@ public Set getSolvers() { .map(s -> new ProblemSolverInfo(s.getId(), s.getName())) .collect(Collectors.toSet()); } - - public List getSubRoutines(String id) { - SolverT solver = getSolver(id); - return solver.getSubRoutines(); - } } diff --git a/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java b/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java index f7e243a8..53d057f3 100644 --- a/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java +++ b/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java @@ -9,13 +9,15 @@ import edu.kit.provideq.toolbox.meta.MetaSolver; import edu.kit.provideq.toolbox.meta.Problem; import edu.kit.provideq.toolbox.meta.ProblemSolver; +import edu.kit.provideq.toolbox.meta.SubRoutineDefinition; import edu.kit.provideq.toolbox.sat.MetaSolverSat; -import org.reactivestreams.Publisher; -import org.reactivestreams.Subscriber; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import org.springdoc.core.fn.builders.arrayschema.Builder; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; import org.springframework.validation.BeanPropertyBindingResult; import org.springframework.validation.Errors; import org.springframework.validation.Validator; @@ -23,14 +25,17 @@ import org.springframework.web.reactive.function.server.RouterFunction; import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.reactive.function.server.ServerResponse; +import org.springframework.web.server.ResponseStatusException; import org.springframework.web.server.ServerWebInputException; import reactor.core.publisher.Mono; -import java.util.Objects; +import java.util.List; import java.util.Set; import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder; +import static org.springdoc.core.fn.builders.arrayschema.Builder.arraySchemaBuilder; import static org.springdoc.core.fn.builders.content.Builder.contentBuilder; +import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder; import static org.springdoc.core.fn.builders.requestbody.Builder.requestBodyBuilder; import static org.springdoc.core.fn.builders.schema.Builder.schemaBuilder; import static org.springdoc.webflux.core.fn.SpringdocRouteBuilder.route; @@ -53,7 +58,7 @@ public SolveRouter(MetaSolverSat sat, MetaSolverMaxCut maxCut, MetaSolverFeature } @Bean - RouterFunction getRoutes() { + RouterFunction getSolveRoutes() { return metaSolvers.stream() .map(this::defineRouteForMetaSolver) .reduce(RouterFunction::and) @@ -77,10 +82,11 @@ private RouterFunction defineRouteForMetaSolver(MetaSolver Mono handleRouteForMetaSolver(MetaSolver metaSolver, ServerRequest req) { var x = req .bodyToMono(new ParameterizedTypeReference>() {}) @@ -89,7 +95,6 @@ private Mono handleRouteForMetaSolver(Meta .map(Solution::toStringSolution); return ok().body(x, new ParameterizedTypeReference<>() {}); } - private void validate(SolveRequest request) { Errors errors = new BeanPropertyBindingResult(request, "request"); validator.validate(request, errors); @@ -122,4 +127,40 @@ private void validate(SolveRequest request) { return solution; } + + @Bean + RouterFunction getSubRoutineRoutes() { + return metaSolvers.stream() + .map(this::defineSubRoutineRouteForMetaSolver) + .reduce(RouterFunction::and) + .orElseThrow(); + } + + private RouterFunction defineSubRoutineRouteForMetaSolver(MetaSolver metaSolver) { + String problemId = metaSolver.getProblemType().getId(); + return route().GET( + "/sub-routines/" + problemId, + req -> handleSubRoutineRouteForMetaSolver(metaSolver, req), + ops -> ops + .operationId("/sub-routines/" + problemId) + .parameter(parameterBuilder().in(ParameterIn.QUERY).name("id")) + .tag(problemId) + .response(responseBuilder() + .responseCode(String.valueOf(HttpStatus.OK.value())) + .content(contentBuilder() + .mediaType(APPLICATION_JSON_VALUE) + .array(arraySchemaBuilder().schema(schemaBuilder().implementation(SubRoutineDefinition.class))) + ) + ) + ).build(); + } + + private Mono handleSubRoutineRouteForMetaSolver(MetaSolver metaSolver, ServerRequest req) { + var subroutines = req.queryParam("id") + .flatMap(metaSolver::getSolver) + .map(ProblemSolver::getSubRoutines) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Could not find a solver for this problem with this solver id!")); + + return ok().body(subroutines, new ParameterizedTypeReference>() {}); + } } diff --git a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/FeatureModelAnomalyController.java b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/FeatureModelAnomalyController.java index 937d0529..e2fbdc5b 100644 --- a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/FeatureModelAnomalyController.java +++ b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/FeatureModelAnomalyController.java @@ -2,24 +2,19 @@ import edu.kit.provideq.toolbox.ProblemController; import edu.kit.provideq.toolbox.ProblemSolverInfo; -import edu.kit.provideq.toolbox.Solution; import edu.kit.provideq.toolbox.SolutionHandle; -import edu.kit.provideq.toolbox.featuremodel.SolveFeatureModelRequest; import edu.kit.provideq.toolbox.featuremodel.anomaly.solvers.FeatureModelAnomalySolver; import edu.kit.provideq.toolbox.meta.MetaSolver; import edu.kit.provideq.toolbox.meta.ProblemType; -import edu.kit.provideq.toolbox.meta.SubRoutineDefinition; import edu.kit.provideq.toolbox.meta.setting.MetaSolverSetting; -import jakarta.validation.Valid; -import java.util.List; -import java.util.Set; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import java.util.List; +import java.util.Set; + @RestController public class FeatureModelAnomalyController extends ProblemController { @@ -77,7 +72,7 @@ private SolutionHandle solveAnomaly(SolveFeatureModelRequest request, var solution = (Solution) super.solve( request.replaceContent(new FeatureModelAnomalyProblem(request.requestContent, anomaly))); solution.setSolverName(solution.getSolverName() + ": " + anomaly.name); - return solution; + return solution; FIXME }*/ @CrossOrigin @@ -86,12 +81,6 @@ public SolutionHandle getSolution(@RequestParam(name = "id") long id) { return super.findSolution(id); } - @CrossOrigin - @GetMapping("/sub-routines/feature-model/anomaly") - public List getSubRoutines(@RequestParam(name = "id") String solverId) { - return super.getSubRoutines(solverId); - } - @CrossOrigin @GetMapping("/meta-solver/settings/feature-model/anomaly") public List getMetaSolverSettings() { diff --git a/src/main/java/edu/kit/provideq/toolbox/maxcut/MaxCutController.java b/src/main/java/edu/kit/provideq/toolbox/maxcut/MaxCutController.java index 1a17e6fe..21a11643 100644 --- a/src/main/java/edu/kit/provideq/toolbox/maxcut/MaxCutController.java +++ b/src/main/java/edu/kit/provideq/toolbox/maxcut/MaxCutController.java @@ -43,12 +43,6 @@ public SolutionHandle getSolution(@RequestParam(name = "id") long id) { return super.findSolution(id); } - @CrossOrigin - @GetMapping("/sub-routines/max-cut") - public List getSubRoutines(@RequestParam(name = "id") String solverId) { - return super.getSubRoutines(solverId); - } - @CrossOrigin @GetMapping("/meta-solver/settings/max-cut") public List getMetaSolverSettings() { diff --git a/src/main/java/edu/kit/provideq/toolbox/meta/MetaSolver.java b/src/main/java/edu/kit/provideq/toolbox/meta/MetaSolver.java index 4e91f412..0a181388 100644 --- a/src/main/java/edu/kit/provideq/toolbox/meta/MetaSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/meta/MetaSolver.java @@ -4,7 +4,9 @@ import edu.kit.provideq.toolbox.SolveRequest; import edu.kit.provideq.toolbox.meta.setting.MetaSolverSetting; import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.server.ResponseStatusException; import reactor.core.publisher.Mono; import java.util.HashSet; @@ -74,13 +76,13 @@ public abstract SolverT findSolver( Problem problem, List metaSolverSettings); - public Optional getSolver(String id) { - if (id == null) { + public Optional getSolver(String solverId) { + if (solverId == null) { return Optional.empty(); } return solvers.stream() - .filter(solver -> solver.getId().equals(id)) + .filter(solver -> solver.getId().equals(solverId)) .findFirst(); } diff --git a/src/main/java/edu/kit/provideq/toolbox/sat/SatController.java b/src/main/java/edu/kit/provideq/toolbox/sat/SatController.java index 6e454400..9ab09527 100644 --- a/src/main/java/edu/kit/provideq/toolbox/sat/SatController.java +++ b/src/main/java/edu/kit/provideq/toolbox/sat/SatController.java @@ -44,12 +44,6 @@ public SolutionHandle getSolution(@RequestParam(name = "id") long id) { return super.findSolution(id).toStringSolution(DimacsCnfSolution::toHumanReadableString); } - @CrossOrigin - @GetMapping("/sub-routines/sat") - public List getSubRoutines(@RequestParam(name = "id") String solverId) { - return super.getSubRoutines(solverId); - } - @CrossOrigin @GetMapping("/meta-solver/settings/sat") public List getMetaSolverSettings() { From faf16066d3fc43eb641f48272062b8aef84b7b92 Mon Sep 17 00:00:00 2001 From: Max Schweikart Date: Sun, 30 Jul 2023 20:02:58 +0200 Subject: [PATCH 05/22] refactor: move /solvers/... endpoints to functional router --- .../provideq/toolbox/ProblemController.java | 8 ---- .../kit/provideq/toolbox/api/SolveRouter.java | 40 ++++++++++++++++--- .../FeatureModelAnomalyController.java | 6 --- .../toolbox/maxcut/MaxCutController.java | 6 --- .../provideq/toolbox/sat/SatController.java | 6 --- 5 files changed, 35 insertions(+), 31 deletions(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/ProblemController.java b/src/main/java/edu/kit/provideq/toolbox/ProblemController.java index afdc5790..2948e0dd 100644 --- a/src/main/java/edu/kit/provideq/toolbox/ProblemController.java +++ b/src/main/java/edu/kit/provideq/toolbox/ProblemController.java @@ -38,12 +38,4 @@ public Solution findSolution(long id) { return solution; } - - public Set getSolvers() { - return getMetaSolver() - .getAllSolvers() - .stream() - .map(s -> new ProblemSolverInfo(s.getId(), s.getName())) - .collect(Collectors.toSet()); - } } diff --git a/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java b/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java index 53d057f3..b38af4b7 100644 --- a/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java +++ b/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java @@ -1,9 +1,6 @@ package edu.kit.provideq.toolbox.api; -import edu.kit.provideq.toolbox.Solution; -import edu.kit.provideq.toolbox.SolutionHandle; -import edu.kit.provideq.toolbox.SolveRequest; -import edu.kit.provideq.toolbox.SubRoutinePool; +import edu.kit.provideq.toolbox.*; import edu.kit.provideq.toolbox.featuremodel.anomaly.MetaSolverFeatureModelAnomaly; import edu.kit.provideq.toolbox.maxcut.MetaSolverMaxCut; import edu.kit.provideq.toolbox.meta.MetaSolver; @@ -12,7 +9,6 @@ import edu.kit.provideq.toolbox.meta.SubRoutineDefinition; import edu.kit.provideq.toolbox.sat.MetaSolverSat; import io.swagger.v3.oas.annotations.enums.ParameterIn; -import org.springdoc.core.fn.builders.arrayschema.Builder; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -163,4 +159,38 @@ private Mono handleSubRoutineRouteForMetaSolver(MetaSolver>() {}); } + + @Bean + RouterFunction getSolversRoutes() { + return metaSolvers.stream() + .map(this::defineSolversRouteForMetaSolver) + .reduce(RouterFunction::and) + .orElseThrow(); + } + + private RouterFunction defineSolversRouteForMetaSolver(MetaSolver metaSolver) { + String problemId = metaSolver.getProblemType().getId(); + return route().GET( + "/solvers/" + problemId, + req -> handleSolversRouteForMetaSolver(metaSolver), + ops -> ops + .operationId("/solvers/" + problemId) + .tag(problemId) + .response(responseBuilder() + .responseCode(String.valueOf(HttpStatus.OK.value())) + .content(contentBuilder() + .mediaType(APPLICATION_JSON_VALUE) + .array(arraySchemaBuilder().schema(schemaBuilder().implementation(ProblemSolverInfo.class))) + ) + ) + ).build(); + } + + private Mono handleSolversRouteForMetaSolver(MetaSolver metaSolver) { + var solvers = metaSolver.getAllSolvers().stream() + .map(solver -> new ProblemSolverInfo(solver.getId(), solver.getName())) + .toList(); + + return ok().body(solvers, new ParameterizedTypeReference>() {}); + } } diff --git a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/FeatureModelAnomalyController.java b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/FeatureModelAnomalyController.java index e2fbdc5b..9fe2f665 100644 --- a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/FeatureModelAnomalyController.java +++ b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/FeatureModelAnomalyController.java @@ -86,10 +86,4 @@ public SolutionHandle getSolution(@RequestParam(name = "id") long id) { public List getMetaSolverSettings() { return metaSolver.getSettings(); } - - @CrossOrigin - @GetMapping("/solvers/feature-model/anomaly") - public Set getSolvers() { - return super.getSolvers(); - } } diff --git a/src/main/java/edu/kit/provideq/toolbox/maxcut/MaxCutController.java b/src/main/java/edu/kit/provideq/toolbox/maxcut/MaxCutController.java index 21a11643..16a0db1c 100644 --- a/src/main/java/edu/kit/provideq/toolbox/maxcut/MaxCutController.java +++ b/src/main/java/edu/kit/provideq/toolbox/maxcut/MaxCutController.java @@ -48,10 +48,4 @@ public SolutionHandle getSolution(@RequestParam(name = "id") long id) { public List getMetaSolverSettings() { return metaSolver.getSettings(); } - - @CrossOrigin - @GetMapping("/solvers/max-cut") - public Set getSolvers() { - return super.getSolvers(); - } } diff --git a/src/main/java/edu/kit/provideq/toolbox/sat/SatController.java b/src/main/java/edu/kit/provideq/toolbox/sat/SatController.java index 9ab09527..82c9fe27 100644 --- a/src/main/java/edu/kit/provideq/toolbox/sat/SatController.java +++ b/src/main/java/edu/kit/provideq/toolbox/sat/SatController.java @@ -49,10 +49,4 @@ public SolutionHandle getSolution(@RequestParam(name = "id") long id) { public List getMetaSolverSettings() { return metaSolver.getSettings(); } - - @CrossOrigin - @GetMapping("/solvers/sat") - public Set getSolvers() { - return super.getSolvers(); - } } From b099156cb7b8bd2ad32daf377bb5340859744056 Mon Sep 17 00:00:00 2001 From: Max Schweikart Date: Sun, 30 Jul 2023 20:08:00 +0200 Subject: [PATCH 06/22] refactor: move meta solver settings endpoints to functional router --- .../kit/provideq/toolbox/api/SolveRouter.java | 31 +++++++++++++++++++ .../FeatureModelAnomalyController.java | 6 ---- .../toolbox/maxcut/MaxCutController.java | 6 ---- .../provideq/toolbox/sat/SatController.java | 6 ---- 4 files changed, 31 insertions(+), 18 deletions(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java b/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java index b38af4b7..587de2c0 100644 --- a/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java +++ b/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java @@ -7,6 +7,7 @@ import edu.kit.provideq.toolbox.meta.Problem; import edu.kit.provideq.toolbox.meta.ProblemSolver; import edu.kit.provideq.toolbox.meta.SubRoutineDefinition; +import edu.kit.provideq.toolbox.meta.setting.MetaSolverSetting; import edu.kit.provideq.toolbox.sat.MetaSolverSat; import io.swagger.v3.oas.annotations.enums.ParameterIn; import org.springframework.context.ApplicationContext; @@ -193,4 +194,34 @@ private Mono handleSolversRouteForMetaSolver(MetaSolver return ok().body(solvers, new ParameterizedTypeReference>() {}); } + + @Bean + RouterFunction getMetaSolverSettingsRoutes() { + return metaSolvers.stream() + .map(this::defineMetaSolverSettingsRouteForMetaSolver) + .reduce(RouterFunction::and) + .orElseThrow(); + } + + private RouterFunction defineMetaSolverSettingsRouteForMetaSolver(MetaSolver metaSolver) { + String problemId = metaSolver.getProblemType().getId(); + return route().GET( + "/meta-solver/settings/" + problemId, + req -> handleMetaSolverSettingsRouteForMetaSolver(metaSolver), + ops -> ops + .operationId("/meta-solver/settings/" + problemId) + .tag(problemId) + .response(responseBuilder() + .responseCode(String.valueOf(HttpStatus.OK.value())) + .content(contentBuilder() + .mediaType(APPLICATION_JSON_VALUE) + .array(arraySchemaBuilder().schema(schemaBuilder().implementation(MetaSolverSetting.class))) + ) + ) + ).build(); + } + + private Mono handleMetaSolverSettingsRouteForMetaSolver(MetaSolver metaSolver) { + return ok().body(metaSolver.getSettings(), new ParameterizedTypeReference>() {}); + } } diff --git a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/FeatureModelAnomalyController.java b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/FeatureModelAnomalyController.java index 9fe2f665..63f0b7fb 100644 --- a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/FeatureModelAnomalyController.java +++ b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/FeatureModelAnomalyController.java @@ -80,10 +80,4 @@ private SolutionHandle solveAnomaly(SolveFeatureModelRequest request, public SolutionHandle getSolution(@RequestParam(name = "id") long id) { return super.findSolution(id); } - - @CrossOrigin - @GetMapping("/meta-solver/settings/feature-model/anomaly") - public List getMetaSolverSettings() { - return metaSolver.getSettings(); - } } diff --git a/src/main/java/edu/kit/provideq/toolbox/maxcut/MaxCutController.java b/src/main/java/edu/kit/provideq/toolbox/maxcut/MaxCutController.java index 16a0db1c..17debecc 100644 --- a/src/main/java/edu/kit/provideq/toolbox/maxcut/MaxCutController.java +++ b/src/main/java/edu/kit/provideq/toolbox/maxcut/MaxCutController.java @@ -42,10 +42,4 @@ public MetaSolver getMetaSolver() { public SolutionHandle getSolution(@RequestParam(name = "id") long id) { return super.findSolution(id); } - - @CrossOrigin - @GetMapping("/meta-solver/settings/max-cut") - public List getMetaSolverSettings() { - return metaSolver.getSettings(); - } } diff --git a/src/main/java/edu/kit/provideq/toolbox/sat/SatController.java b/src/main/java/edu/kit/provideq/toolbox/sat/SatController.java index 82c9fe27..6bcc627e 100644 --- a/src/main/java/edu/kit/provideq/toolbox/sat/SatController.java +++ b/src/main/java/edu/kit/provideq/toolbox/sat/SatController.java @@ -43,10 +43,4 @@ public MetaSolver getMetaSolver() { public SolutionHandle getSolution(@RequestParam(name = "id") long id) { return super.findSolution(id).toStringSolution(DimacsCnfSolution::toHumanReadableString); } - - @CrossOrigin - @GetMapping("/meta-solver/settings/sat") - public List getMetaSolverSettings() { - return metaSolver.getSettings(); - } } From 8c9c5bb7fe7fbc1445795075db0a6f6484efe523 Mon Sep 17 00:00:00 2001 From: Max Schweikart Date: Sun, 30 Jul 2023 20:38:09 +0200 Subject: [PATCH 07/22] refactor: move GET /solve/... routes to functional router --- .../kit/provideq/toolbox/api/SolveRouter.java | 34 +++++++++++++++++++ .../FeatureModelAnomalyController.java | 6 ---- .../toolbox/maxcut/MaxCutController.java | 6 ---- .../provideq/toolbox/sat/SatController.java | 6 ---- 4 files changed, 34 insertions(+), 18 deletions(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java b/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java index 587de2c0..4740e98f 100644 --- a/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java +++ b/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java @@ -224,4 +224,38 @@ private RouterFunction defineMetaSolverSettingsRouteForMetaSolve private Mono handleMetaSolverSettingsRouteForMetaSolver(MetaSolver metaSolver) { return ok().body(metaSolver.getSettings(), new ParameterizedTypeReference>() {}); } + + @Bean + RouterFunction getSolutionRoutes() { + return metaSolvers.stream() + .map(this::defineSolutionRouteForMetaSolver) + .reduce(RouterFunction::and) + .orElseThrow(); // we should always have at least one route or the toolbox is useless + } + + private RouterFunction defineSolutionRouteForMetaSolver(MetaSolver metaSolver) { + String problemId = metaSolver.getProblemType().getId(); + return route().GET( + "/solve/" + problemId, + accept(APPLICATION_JSON), + req -> handleSolutionRouteForMetaSolver(metaSolver, req), + ops -> ops + .operationId("/solution/" + problemId) + .tag(problemId) + .parameter(parameterBuilder().in(ParameterIn.QUERY).name("id")) + .response(responseBuilder() + .responseCode(String.valueOf(HttpStatus.OK.value())).implementation(SolutionHandle.class) + ) + ).build(); + } + + private Mono handleSolutionRouteForMetaSolver(MetaSolver metaSolver, ServerRequest req) { + var solution = req.queryParam("id") + .map(Long::parseLong) + .map(id -> metaSolver.getSolutionManager().getSolution(id)) + .map(Solution::toStringSolution); + + // yes, solution is of type `Solution`. No idea why `toStringSolution` returns `SolutionHandle` + return ok().body(solution, new ParameterizedTypeReference>() {}); + } } diff --git a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/FeatureModelAnomalyController.java b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/FeatureModelAnomalyController.java index 63f0b7fb..12b03353 100644 --- a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/FeatureModelAnomalyController.java +++ b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/FeatureModelAnomalyController.java @@ -74,10 +74,4 @@ private SolutionHandle solveAnomaly(SolveFeatureModelRequest request, solution.setSolverName(solution.getSolverName() + ": " + anomaly.name); return solution; FIXME }*/ - - @CrossOrigin - @GetMapping("/solve/feature-model/anomaly/") - public SolutionHandle getSolution(@RequestParam(name = "id") long id) { - return super.findSolution(id); - } } diff --git a/src/main/java/edu/kit/provideq/toolbox/maxcut/MaxCutController.java b/src/main/java/edu/kit/provideq/toolbox/maxcut/MaxCutController.java index 17debecc..fa3cb28c 100644 --- a/src/main/java/edu/kit/provideq/toolbox/maxcut/MaxCutController.java +++ b/src/main/java/edu/kit/provideq/toolbox/maxcut/MaxCutController.java @@ -36,10 +36,4 @@ public ProblemType getProblemType() { public MetaSolver getMetaSolver() { return metaSolver; } - - @CrossOrigin - @GetMapping("/solve/max-cut") - public SolutionHandle getSolution(@RequestParam(name = "id") long id) { - return super.findSolution(id); - } } diff --git a/src/main/java/edu/kit/provideq/toolbox/sat/SatController.java b/src/main/java/edu/kit/provideq/toolbox/sat/SatController.java index 6bcc627e..4679b7fd 100644 --- a/src/main/java/edu/kit/provideq/toolbox/sat/SatController.java +++ b/src/main/java/edu/kit/provideq/toolbox/sat/SatController.java @@ -37,10 +37,4 @@ public ProblemType getProblemType() { public MetaSolver getMetaSolver() { return metaSolver; } - - @CrossOrigin - @GetMapping("/solve/sat") - public SolutionHandle getSolution(@RequestParam(name = "id") long id) { - return super.findSolution(id).toStringSolution(DimacsCnfSolution::toHumanReadableString); - } } From a851a677ba6720ec02391f04b64fa0a31456fcb5 Mon Sep 17 00:00:00 2001 From: Max Schweikart Date: Sun, 30 Jul 2023 20:50:04 +0200 Subject: [PATCH 08/22] refactor: remove ProblemController --- .../provideq/toolbox/MetaSolverProvider.java | 36 +++++++++ .../provideq/toolbox/ProblemController.java | 41 ---------- .../toolbox/ProblemControllerProvider.java | 29 ------- .../kit/provideq/toolbox/SubRoutinePool.java | 26 +++---- .../kit/provideq/toolbox/api/SolveRouter.java | 51 +++--------- .../FeatureModelAnomalyController.java | 77 ------------------- .../toolbox/maxcut/MaxCutController.java | 39 ---------- .../kit/provideq/toolbox/meta/MetaSolver.java | 34 ++++++++ .../provideq/toolbox/sat/SatController.java | 40 ---------- 9 files changed, 89 insertions(+), 284 deletions(-) create mode 100644 src/main/java/edu/kit/provideq/toolbox/MetaSolverProvider.java delete mode 100644 src/main/java/edu/kit/provideq/toolbox/ProblemController.java delete mode 100644 src/main/java/edu/kit/provideq/toolbox/ProblemControllerProvider.java delete mode 100644 src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/FeatureModelAnomalyController.java delete mode 100644 src/main/java/edu/kit/provideq/toolbox/maxcut/MaxCutController.java delete mode 100644 src/main/java/edu/kit/provideq/toolbox/sat/SatController.java diff --git a/src/main/java/edu/kit/provideq/toolbox/MetaSolverProvider.java b/src/main/java/edu/kit/provideq/toolbox/MetaSolverProvider.java new file mode 100644 index 00000000..ec8b13e1 --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/MetaSolverProvider.java @@ -0,0 +1,36 @@ +package edu.kit.provideq.toolbox; + +import edu.kit.provideq.toolbox.meta.MetaSolver; +import edu.kit.provideq.toolbox.meta.ProblemType; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.stream.Collectors; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; + +@Component +public class MetaSolverProvider { + private final Map> metaSolvers; + + @Autowired + public MetaSolverProvider(ApplicationContext context) { + this.metaSolvers = + context.getBeansOfType(MetaSolver.class) + .values() + .stream() + .collect(Collectors.toMap( + MetaSolver::getProblemType, + metaSolver -> (MetaSolver) metaSolver)); + } + + public MetaSolver getMetaSolver(ProblemType problemType) { + return metaSolvers.get(problemType); + } + + public Collection> getMetaSolvers() { + return Collections.unmodifiableCollection(metaSolvers.values()); + } +} diff --git a/src/main/java/edu/kit/provideq/toolbox/ProblemController.java b/src/main/java/edu/kit/provideq/toolbox/ProblemController.java deleted file mode 100644 index 2948e0dd..00000000 --- a/src/main/java/edu/kit/provideq/toolbox/ProblemController.java +++ /dev/null @@ -1,41 +0,0 @@ -package edu.kit.provideq.toolbox; - -import edu.kit.provideq.toolbox.meta.MetaSolver; -import edu.kit.provideq.toolbox.meta.ProblemSolver; -import edu.kit.provideq.toolbox.meta.ProblemType; -import edu.kit.provideq.toolbox.meta.SubRoutineDefinition; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; -import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Component; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.server.ResponseStatusException; - -/** - * Abstract controller, offers generic post and get methods. - * - * @param the type in which problem input is expected to arrive - * @param the type in which a solution will be formatted - * @param the type of solver that is to be used to solve a problem - */ -@Component -@RestController -public abstract class ProblemController> { - private final SolutionManager solutionManager = new SolutionManager<>(); - - public abstract ProblemType getProblemType(); - - public abstract MetaSolver getMetaSolver(); - - public Solution findSolution(long id) { - var solution = solutionManager.getSolution(id); - if (solution == null) { - throw new ResponseStatusException(HttpStatus.NOT_FOUND, - String.format("Unable to find solution process with id %d", id)); - } - - return solution; - } -} diff --git a/src/main/java/edu/kit/provideq/toolbox/ProblemControllerProvider.java b/src/main/java/edu/kit/provideq/toolbox/ProblemControllerProvider.java deleted file mode 100644 index c1398920..00000000 --- a/src/main/java/edu/kit/provideq/toolbox/ProblemControllerProvider.java +++ /dev/null @@ -1,29 +0,0 @@ -package edu.kit.provideq.toolbox; - -import edu.kit.provideq.toolbox.meta.ProblemType; -import java.util.Map; -import java.util.stream.Collectors; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.stereotype.Component; - -@Component -public class ProblemControllerProvider { - private final Map> problemControllers; - - @Autowired - public ProblemControllerProvider( - ApplicationContext context) { - problemControllers = - context.getBeansOfType(ProblemController.class) - .values() - .stream() - .collect(Collectors.toMap( - ProblemController::getProblemType, - problemController -> (ProblemController) problemController)); - } - - public ProblemController getProblemController(ProblemType problemType) { - return problemControllers.get(problemType); - } -} diff --git a/src/main/java/edu/kit/provideq/toolbox/SubRoutinePool.java b/src/main/java/edu/kit/provideq/toolbox/SubRoutinePool.java index bfbae979..4bd13e9c 100644 --- a/src/main/java/edu/kit/provideq/toolbox/SubRoutinePool.java +++ b/src/main/java/edu/kit/provideq/toolbox/SubRoutinePool.java @@ -1,5 +1,6 @@ package edu.kit.provideq.toolbox; +import edu.kit.provideq.toolbox.meta.MetaSolver; import edu.kit.provideq.toolbox.meta.ProblemSolver; import edu.kit.provideq.toolbox.meta.ProblemType; import java.util.Collections; @@ -15,7 +16,7 @@ public class SubRoutinePool { private final Map> subRoutineCalls; - private ProblemControllerProvider problemControllerProvider; + private MetaSolverProvider metaSolverProvider; public SubRoutinePool() { subRoutineCalls = Collections.emptyMap(); @@ -31,8 +32,8 @@ public SubRoutinePool(Map> requestedSubRoutines) { } @Autowired - public void setProblemControllerProvider(ProblemControllerProvider problemControllerProvider) { - this.problemControllerProvider = problemControllerProvider; + public void setProblemControllerProvider(MetaSolverProvider metaSolverProvider) { + this.metaSolverProvider = metaSolverProvider; } /** @@ -45,8 +46,7 @@ public void setProblemControllerProvider(ProblemControllerProvider problemContro */ public Function> getSubRoutine( ProblemType problemType) { - return null; // FIXME - /*return content -> { + return content -> { SolveRequest subRoutine = subRoutineCalls.get(problemType); if (subRoutine == null) { subRoutine = new SolveRequest<>(); @@ -54,16 +54,10 @@ public Function> getSubRouti var newSolveRequest = subRoutine.replaceContent(content); - ProblemController< - ProblemT, - SolutionT, - ? extends ProblemSolver> problemController - = (ProblemController< - ProblemT, - SolutionT, - ? extends ProblemSolver>) - problemControllerProvider.getProblemController(problemType); - return problemController.solve(newSolveRequest); - };*/ + MetaSolver> metaSolver = + (MetaSolver>) + metaSolverProvider.getMetaSolver(problemType); + return metaSolver.solve(newSolveRequest); + }; } } diff --git a/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java b/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java index 4740e98f..d46c85c9 100644 --- a/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java +++ b/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java @@ -1,16 +1,11 @@ package edu.kit.provideq.toolbox.api; import edu.kit.provideq.toolbox.*; -import edu.kit.provideq.toolbox.featuremodel.anomaly.MetaSolverFeatureModelAnomaly; -import edu.kit.provideq.toolbox.maxcut.MetaSolverMaxCut; import edu.kit.provideq.toolbox.meta.MetaSolver; -import edu.kit.provideq.toolbox.meta.Problem; import edu.kit.provideq.toolbox.meta.ProblemSolver; import edu.kit.provideq.toolbox.meta.SubRoutineDefinition; import edu.kit.provideq.toolbox.meta.setting.MetaSolverSetting; -import edu.kit.provideq.toolbox.sat.MetaSolverSat; import io.swagger.v3.oas.annotations.enums.ParameterIn; -import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.ParameterizedTypeReference; @@ -27,7 +22,6 @@ import reactor.core.publisher.Mono; import java.util.List; -import java.util.Set; import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder; import static org.springdoc.core.fn.builders.arrayschema.Builder.arraySchemaBuilder; @@ -44,19 +38,17 @@ @Configuration @EnableWebFlux public class SolveRouter { - private final Set> metaSolvers; - private final ApplicationContext context; + private final MetaSolverProvider metaSolverProvider; private final Validator validator; - public SolveRouter(MetaSolverSat sat, MetaSolverMaxCut maxCut, MetaSolverFeatureModelAnomaly featureModelAnomaly, ApplicationContext context, Validator validator) { - this.metaSolvers = Set.of(sat, maxCut, featureModelAnomaly); - this.context = context; + public SolveRouter(MetaSolverProvider metaSolverProvider, Validator validator) { + this.metaSolverProvider = metaSolverProvider; this.validator = validator; } @Bean RouterFunction getSolveRoutes() { - return metaSolvers.stream() + return metaSolverProvider.getMetaSolvers().stream() .map(this::defineRouteForMetaSolver) .reduce(RouterFunction::and) .orElseThrow(); // we should always have at least one route or the toolbox is useless @@ -88,7 +80,7 @@ private Mono handleRouteForMetaSolver(Meta var x = req .bodyToMono(new ParameterizedTypeReference>() {}) .doOnNext(this::validate) - .map(request -> solve(metaSolver, request)) + .map(metaSolver::solve) .map(Solution::toStringSolution); return ok().body(x, new ParameterizedTypeReference<>() {}); } @@ -100,34 +92,9 @@ private void validate(SolveRequest request) { } } - private > Solution - solve(MetaSolver metaSolver, SolveRequest request) { - Solution solution = metaSolver.getSolutionManager().createSolution(); - Problem problem = new Problem<>(request.requestContent, metaSolver.getProblemType()); - - SolverT solver = metaSolver - .getSolver(request.requestedSolverId) - .orElseGet(() -> metaSolver.findSolver(problem, request.requestedMetaSolverSettings)); - - solution.setSolverName(solver.getName()); - - SubRoutinePool subRoutinePool = - request.requestedSubSolveRequests == null - ? context.getBean(SubRoutinePool.class) - : context.getBean(SubRoutinePool.class, request.requestedSubSolveRequests); - - long start = System.currentTimeMillis(); - solver.solve(problem, solution, subRoutinePool); - long finish = System.currentTimeMillis(); - - solution.setExecutionMilliseconds(finish - start); - - return solution; - } - @Bean RouterFunction getSubRoutineRoutes() { - return metaSolvers.stream() + return metaSolverProvider.getMetaSolvers().stream() .map(this::defineSubRoutineRouteForMetaSolver) .reduce(RouterFunction::and) .orElseThrow(); @@ -163,7 +130,7 @@ private Mono handleSubRoutineRouteForMetaSolver(MetaSolver getSolversRoutes() { - return metaSolvers.stream() + return metaSolverProvider.getMetaSolvers().stream() .map(this::defineSolversRouteForMetaSolver) .reduce(RouterFunction::and) .orElseThrow(); @@ -197,7 +164,7 @@ private Mono handleSolversRouteForMetaSolver(MetaSolver @Bean RouterFunction getMetaSolverSettingsRoutes() { - return metaSolvers.stream() + return metaSolverProvider.getMetaSolvers().stream() .map(this::defineMetaSolverSettingsRouteForMetaSolver) .reduce(RouterFunction::and) .orElseThrow(); @@ -227,7 +194,7 @@ private Mono handleMetaSolverSettingsRouteForMetaSolver(MetaSolv @Bean RouterFunction getSolutionRoutes() { - return metaSolvers.stream() + return metaSolverProvider.getMetaSolvers().stream() .map(this::defineSolutionRouteForMetaSolver) .reduce(RouterFunction::and) .orElseThrow(); // we should always have at least one route or the toolbox is useless diff --git a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/FeatureModelAnomalyController.java b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/FeatureModelAnomalyController.java deleted file mode 100644 index 12b03353..00000000 --- a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/FeatureModelAnomalyController.java +++ /dev/null @@ -1,77 +0,0 @@ -package edu.kit.provideq.toolbox.featuremodel.anomaly; - -import edu.kit.provideq.toolbox.ProblemController; -import edu.kit.provideq.toolbox.ProblemSolverInfo; -import edu.kit.provideq.toolbox.SolutionHandle; -import edu.kit.provideq.toolbox.featuremodel.anomaly.solvers.FeatureModelAnomalySolver; -import edu.kit.provideq.toolbox.meta.MetaSolver; -import edu.kit.provideq.toolbox.meta.ProblemType; -import edu.kit.provideq.toolbox.meta.setting.MetaSolverSetting; -import org.springframework.web.bind.annotation.CrossOrigin; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -import java.util.List; -import java.util.Set; - -@RestController -public class FeatureModelAnomalyController - extends ProblemController { - - private final MetaSolver< - FeatureModelAnomalyProblem, - String, - FeatureModelAnomalySolver> metaSolver; - - public FeatureModelAnomalyController(MetaSolver< - FeatureModelAnomalyProblem, - String, - FeatureModelAnomalySolver> metaSolver) { - this.metaSolver = metaSolver; - } - - @Override - public ProblemType getProblemType() { - return ProblemType.FEATURE_MODEL_ANOMALY; - } - - @Override - public MetaSolver getMetaSolver() { - return metaSolver; - } - - /*@CrossOrigin - @PostMapping("/solve/feature-model/anomaly/void") - public SolutionHandle findVoidFeatureModel(@RequestBody @Valid SolveFeatureModelRequest request) { - return solveAnomaly(request, FeatureModelAnomaly.VOID); - } - - @CrossOrigin - @PostMapping("/solve/feature-model/anomaly/dead") - public SolutionHandle findDeadFeatures(@RequestBody @Valid SolveFeatureModelRequest request) { - return solveAnomaly(request, FeatureModelAnomaly.DEAD); - } - - @CrossOrigin - @PostMapping("/solve/feature-model/anomaly/false-optional") - public SolutionHandle findFalseOptionalFeatures( - @RequestBody @Valid SolveFeatureModelRequest request) { - return solveAnomaly(request, FeatureModelAnomaly.FALSE_OPTIONAL); - } - - @CrossOrigin - @PostMapping("/solve/feature-model/anomaly/redundant-constraints") - public SolutionHandle findRedundantConstraints( - @RequestBody @Valid SolveFeatureModelRequest request) { - return solveAnomaly(request, FeatureModelAnomaly.REDUNDANT_CONSTRAINTS); - } - - private SolutionHandle solveAnomaly(SolveFeatureModelRequest request, - FeatureModelAnomaly anomaly) { - var solution = (Solution) super.solve( - request.replaceContent(new FeatureModelAnomalyProblem(request.requestContent, anomaly))); - solution.setSolverName(solution.getSolverName() + ": " + anomaly.name); - return solution; FIXME - }*/ -} diff --git a/src/main/java/edu/kit/provideq/toolbox/maxcut/MaxCutController.java b/src/main/java/edu/kit/provideq/toolbox/maxcut/MaxCutController.java deleted file mode 100644 index fa3cb28c..00000000 --- a/src/main/java/edu/kit/provideq/toolbox/maxcut/MaxCutController.java +++ /dev/null @@ -1,39 +0,0 @@ -package edu.kit.provideq.toolbox.maxcut; - -import edu.kit.provideq.toolbox.ProblemController; -import edu.kit.provideq.toolbox.ProblemSolverInfo; -import edu.kit.provideq.toolbox.SolutionHandle; -import edu.kit.provideq.toolbox.maxcut.solvers.MaxCutSolver; -import edu.kit.provideq.toolbox.meta.MetaSolver; -import edu.kit.provideq.toolbox.meta.ProblemType; -import edu.kit.provideq.toolbox.meta.SubRoutineDefinition; -import edu.kit.provideq.toolbox.meta.setting.MetaSolverSetting; -import jakarta.validation.Valid; -import java.util.List; -import java.util.Set; -import org.springframework.web.bind.annotation.CrossOrigin; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -@RestController -public class MaxCutController extends ProblemController { - - private final MetaSolver metaSolver; - - public MaxCutController(MetaSolver metaSolver) { - this.metaSolver = metaSolver; - } - - @Override - public ProblemType getProblemType() { - return ProblemType.MAX_CUT; - } - - @Override - public MetaSolver getMetaSolver() { - return metaSolver; - } -} diff --git a/src/main/java/edu/kit/provideq/toolbox/meta/MetaSolver.java b/src/main/java/edu/kit/provideq/toolbox/meta/MetaSolver.java index 0a181388..480da1dc 100644 --- a/src/main/java/edu/kit/provideq/toolbox/meta/MetaSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/meta/MetaSolver.java @@ -1,8 +1,12 @@ package edu.kit.provideq.toolbox.meta; +import edu.kit.provideq.toolbox.Solution; import edu.kit.provideq.toolbox.SolutionManager; import edu.kit.provideq.toolbox.SolveRequest; +import edu.kit.provideq.toolbox.SubRoutinePool; import edu.kit.provideq.toolbox.meta.setting.MetaSolverSetting; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpStatus; import org.springframework.web.reactive.function.server.ServerRequest; @@ -28,6 +32,7 @@ public abstract class MetaSolver< SolverT extends ProblemSolver> { private final SolutionManager solutionManager = new SolutionManager<>(); + private ApplicationContext context; protected Set solvers = new HashSet<>(); private ProblemType problemType; @@ -46,6 +51,11 @@ public MetaSolver(ProblemType problemType, SolverT... problemSolvers) { this.problemType = problemType; } + @Autowired + public void setContext(ApplicationContext context) { + this.context = context; + } + /** * Adds a new solver to this meta solvers list of known solvers. * @@ -111,4 +121,28 @@ public ProblemType getProblemType() { public SolutionManager getSolutionManager() { return solutionManager; } + + public Solution solve(SolveRequest request) { + Solution solution = this.getSolutionManager().createSolution(); + Problem problem = new Problem<>(request.requestContent, this.getProblemType()); + + SolverT solver = this + .getSolver(request.requestedSolverId) + .orElseGet(() -> this.findSolver(problem, request.requestedMetaSolverSettings)); + + solution.setSolverName(solver.getName()); + + SubRoutinePool subRoutinePool = + request.requestedSubSolveRequests == null + ? context.getBean(SubRoutinePool.class) + : context.getBean(SubRoutinePool.class, request.requestedSubSolveRequests); + + long start = System.currentTimeMillis(); + solver.solve(problem, solution, subRoutinePool); + long finish = System.currentTimeMillis(); + + solution.setExecutionMilliseconds(finish - start); + + return solution; + } } diff --git a/src/main/java/edu/kit/provideq/toolbox/sat/SatController.java b/src/main/java/edu/kit/provideq/toolbox/sat/SatController.java deleted file mode 100644 index 4679b7fd..00000000 --- a/src/main/java/edu/kit/provideq/toolbox/sat/SatController.java +++ /dev/null @@ -1,40 +0,0 @@ -package edu.kit.provideq.toolbox.sat; - -import edu.kit.provideq.toolbox.ProblemController; -import edu.kit.provideq.toolbox.ProblemSolverInfo; -import edu.kit.provideq.toolbox.SolutionHandle; -import edu.kit.provideq.toolbox.format.cnf.dimacs.DimacsCnfSolution; -import edu.kit.provideq.toolbox.meta.MetaSolver; -import edu.kit.provideq.toolbox.meta.ProblemType; -import edu.kit.provideq.toolbox.meta.SubRoutineDefinition; -import edu.kit.provideq.toolbox.meta.setting.MetaSolverSetting; -import edu.kit.provideq.toolbox.sat.solvers.SatSolver; -import jakarta.validation.Valid; -import java.util.List; -import java.util.Set; -import org.springframework.web.bind.annotation.CrossOrigin; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -@RestController -public class SatController extends ProblemController { - - private final MetaSolver metaSolver; - - public SatController(MetaSolver metaSolver) { - this.metaSolver = metaSolver; - } - - @Override - public ProblemType getProblemType() { - return ProblemType.SAT; - } - - @Override - public MetaSolver getMetaSolver() { - return metaSolver; - } -} From cfcd84cc3b48ae10dff2aed7bab1e5f3fe69e4d1 Mon Sep 17 00:00:00 2001 From: Max Schweikart Date: Sun, 30 Jul 2023 22:02:10 +0200 Subject: [PATCH 09/22] fix: use monos for response bodies --- .../kit/provideq/toolbox/api/SolveRouter.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java b/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java index d46c85c9..6c5cfed2 100644 --- a/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java +++ b/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java @@ -77,12 +77,12 @@ private RouterFunction defineRouteForMetaSolver(MetaSolver Mono handleRouteForMetaSolver(MetaSolver metaSolver, ServerRequest req) { - var x = req + var solutionMono = req .bodyToMono(new ParameterizedTypeReference>() {}) .doOnNext(this::validate) .map(metaSolver::solve) .map(Solution::toStringSolution); - return ok().body(x, new ParameterizedTypeReference<>() {}); + return ok().body(solutionMono, new ParameterizedTypeReference<>() {}); } private void validate(SolveRequest request) { Errors errors = new BeanPropertyBindingResult(request, "request"); @@ -102,6 +102,7 @@ RouterFunction getSubRoutineRoutes() { private RouterFunction defineSubRoutineRouteForMetaSolver(MetaSolver metaSolver) { String problemId = metaSolver.getProblemType().getId(); + System.out.println("x"); return route().GET( "/sub-routines/" + problemId, req -> handleSubRoutineRouteForMetaSolver(metaSolver, req), @@ -125,7 +126,7 @@ private Mono handleSubRoutineRouteForMetaSolver(MetaSolver new ResponseStatusException(HttpStatus.NOT_FOUND, "Could not find a solver for this problem with this solver id!")); - return ok().body(subroutines, new ParameterizedTypeReference>() {}); + return ok().body(Mono.just(subroutines), new ParameterizedTypeReference<>() {}); } @Bean @@ -159,7 +160,7 @@ private Mono handleSolversRouteForMetaSolver(MetaSolver .map(solver -> new ProblemSolverInfo(solver.getId(), solver.getName())) .toList(); - return ok().body(solvers, new ParameterizedTypeReference>() {}); + return ok().body(Mono.just(solvers), new ParameterizedTypeReference<>() {}); } @Bean @@ -189,7 +190,7 @@ private RouterFunction defineMetaSolverSettingsRouteForMetaSolve } private Mono handleMetaSolverSettingsRouteForMetaSolver(MetaSolver metaSolver) { - return ok().body(metaSolver.getSettings(), new ParameterizedTypeReference>() {}); + return ok().body(Mono.just(metaSolver.getSettings()), new ParameterizedTypeReference<>() {}); } @Bean @@ -219,10 +220,11 @@ private RouterFunction defineSolutionRouteForMetaSolver(MetaSolv private Mono handleSolutionRouteForMetaSolver(MetaSolver metaSolver, ServerRequest req) { var solution = req.queryParam("id") .map(Long::parseLong) - .map(id -> metaSolver.getSolutionManager().getSolution(id)) - .map(Solution::toStringSolution); + .map(solutionId -> metaSolver.getSolutionManager().getSolution(solutionId)) + .map(Solution::toStringSolution) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Could not find a solution for this problem with this solution id!")); // yes, solution is of type `Solution`. No idea why `toStringSolution` returns `SolutionHandle` - return ok().body(solution, new ParameterizedTypeReference>() {}); + return ok().body(Mono.just(solution), new ParameterizedTypeReference>() {}); } } From 7eb2c7a1ef307ac5c2457c45013b2790b1b64cee Mon Sep 17 00:00:00 2001 From: Max Schweikart Date: Sun, 30 Jul 2023 23:30:47 +0200 Subject: [PATCH 10/22] fix: import required beans in api tests --- .gitignore | 1 + .../kit/provideq/toolbox/api/SolveRouter.java | 1 - .../api/FeatureModelAnomalySolverTest.java | 67 ++++++++--------- .../toolbox/api/MaxCutSolversTest.java | 73 ++++++++++--------- .../provideq/toolbox/api/SatSolverTest.java | 69 +++++++++--------- 5 files changed, 105 insertions(+), 106 deletions(-) diff --git a/.gitignore b/.gitignore index 2a1cf43c..ca4eeedb 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,4 @@ gamslice.txt # Listing files compiled from our GAMS scripts *.lst *.op2 +gams/**/225a diff --git a/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java b/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java index 6c5cfed2..db270781 100644 --- a/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java +++ b/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java @@ -102,7 +102,6 @@ RouterFunction getSubRoutineRoutes() { private RouterFunction defineSubRoutineRouteForMetaSolver(MetaSolver metaSolver) { String problemId = metaSolver.getProblemType().getId(); - System.out.println("x"); return route().GET( "/sub-routines/" + problemId, req -> handleSubRoutineRouteForMetaSolver(metaSolver, req), diff --git a/src/test/java/edu/kit/provideq/toolbox/api/FeatureModelAnomalySolverTest.java b/src/test/java/edu/kit/provideq/toolbox/api/FeatureModelAnomalySolverTest.java index 5b2191b5..729d1f06 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/FeatureModelAnomalySolverTest.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/FeatureModelAnomalySolverTest.java @@ -1,39 +1,44 @@ package edu.kit.provideq.toolbox.api; -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -import com.fasterxml.jackson.databind.JavaType; -import com.fasterxml.jackson.databind.ObjectMapper; +import edu.kit.provideq.toolbox.MetaSolverProvider; import edu.kit.provideq.toolbox.Solution; import edu.kit.provideq.toolbox.SolutionStatus; +import edu.kit.provideq.toolbox.SubRoutinePool; import edu.kit.provideq.toolbox.featuremodel.SolveFeatureModelRequest; +import edu.kit.provideq.toolbox.featuremodel.anomaly.MetaSolverFeatureModelAnomaly; import edu.kit.provideq.toolbox.featuremodel.anomaly.solvers.FeatureModelAnomalySolver; -import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; +import org.springframework.context.annotation.Import; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.reactive.server.WebTestClient; -@SpringBootTest -@AutoConfigureMockMvc -public class FeatureModelAnomalySolverTest { - @Autowired - private MockMvc mvc; +import java.util.stream.Stream; +import static edu.kit.provideq.toolbox.SolutionStatus.SOLVED; +import static org.hamcrest.Matchers.is; + +@WebFluxTest +@Import(value = { + SolveRouter.class, + MetaSolverProvider.class, + MetaSolverFeatureModelAnomaly.class, + FeatureModelAnomalySolver.class, + SubRoutinePool.class +}) +public class FeatureModelAnomalySolverTest { @Autowired - private ObjectMapper mapper; + private WebTestClient client; public static Stream provideAnomalySolverIds() { String solverId = FeatureModelAnomalySolver.class.getName(); return Stream.of( - Arguments.of(solverId, "void", SolutionStatus.SOLVED), - Arguments.of(solverId, "dead", SolutionStatus.SOLVED), + Arguments.of(solverId, "void", SOLVED), + Arguments.of(solverId, "dead", SOLVED), // not implemented yet, change to SOLVED when they have been implemented! Arguments.of(solverId, "false-optional", SolutionStatus.INVALID), @@ -44,7 +49,7 @@ public static Stream provideAnomalySolverIds() { @ParameterizedTest @MethodSource("provideAnomalySolverIds") void testFeatureModelAnomalySolver(String solverId, String anomalyType, - SolutionStatus expectedStatus) throws Exception { + SolutionStatus expectedStatus) { var req = new SolveFeatureModelRequest(); req.requestedSolverId = solverId; req.requestContent = """ @@ -79,22 +84,14 @@ void testFeatureModelAnomalySolver(String solverId, String anomalyType, Lettuce """; - var requestBuilder = MockMvcRequestBuilders - .post("/solve/feature-model/anomaly/" + anomalyType) - .accept(MediaType.APPLICATION_JSON) - .contentType(MediaType.APPLICATION_JSON) - .content(mapper.writeValueAsString(req)); - - var result = mvc.perform(requestBuilder) - .andExpect(status().isOk()) - .andReturn() - .getResponse().getContentAsString(); - - JavaType solutionType = - mapper.getTypeFactory().constructParametricType(Solution.class, String.class); - Solution solution = mapper.readValue(result, solutionType); + var response = client.post() + .uri("/solve/feature-model-anomaly/" + anomalyType) // FIXME type of anomaly? + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(req) + .exchange(); - assertThat(solution.getStatus()) - .isSameAs(expectedStatus); + response.expectStatus().isOk(); + response.expectBody(new ParameterizedTypeReference>() {}) + .value(Solution::getStatus, is(expectedStatus)); } } diff --git a/src/test/java/edu/kit/provideq/toolbox/api/MaxCutSolversTest.java b/src/test/java/edu/kit/provideq/toolbox/api/MaxCutSolversTest.java index 09c82ebe..8e86ec9a 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/MaxCutSolversTest.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/MaxCutSolversTest.java @@ -1,33 +1,44 @@ package edu.kit.provideq.toolbox.api; -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -import com.fasterxml.jackson.databind.JavaType; -import com.fasterxml.jackson.databind.ObjectMapper; -import edu.kit.provideq.toolbox.Solution; -import edu.kit.provideq.toolbox.SolutionStatus; +import edu.kit.provideq.toolbox.*; +import edu.kit.provideq.toolbox.maxcut.MetaSolverMaxCut; import edu.kit.provideq.toolbox.maxcut.SolveMaxCutRequest; import edu.kit.provideq.toolbox.maxcut.solvers.GamsMaxCutSolver; import edu.kit.provideq.toolbox.maxcut.solvers.QiskitMaxCutSolver; -import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.reactive.server.WebTestClient; -@SpringBootTest -@AutoConfigureMockMvc -class MaxCutSolversTest { - @Autowired - private MockMvc mvc; +import java.util.stream.Stream; +import static edu.kit.provideq.toolbox.SolutionStatus.SOLVED; +import static org.hamcrest.Matchers.is; + +@WebFluxTest +@Import(value = { + SolveRouter.class, + MetaSolverProvider.class, + MetaSolverMaxCut.class, + QiskitMaxCutSolver.class, + GamsMaxCutSolver.class, + QiskitMaxCutSolver.class, + SubRoutinePool.class, + GamsProcessRunner.class, + PythonProcessRunner.class, + ResourceProvider.class +}) +class MaxCutSolversTest { @Autowired - private ObjectMapper mapper; + private WebTestClient client; public static Stream provideMaxCutSolverIds() { return Stream.of( @@ -38,7 +49,7 @@ public static Stream provideMaxCutSolverIds() { @ParameterizedTest @MethodSource("provideMaxCutSolverIds") - void testMaxCutSolver(String solverId) throws Exception { + void testMaxCutSolver(String solverId) { var req = new SolveMaxCutRequest(); req.requestedSolverId = solverId; req.requestContent = """ @@ -70,22 +81,14 @@ void testMaxCutSolver(String solverId) throws Exception { ] ]"""; - var requestBuilder = MockMvcRequestBuilders - .post("/solve/max-cut") - .accept(MediaType.APPLICATION_JSON) - .contentType(MediaType.APPLICATION_JSON) - .content(mapper.writeValueAsString(req)); - - var result = mvc.perform(requestBuilder) - .andExpect(status().isOk()) - .andReturn() - .getResponse().getContentAsString(); - - JavaType solutionType = - mapper.getTypeFactory().constructParametricType(Solution.class, String.class); - Solution solution = mapper.readValue(result, solutionType); + var response = client.post() + .uri("/solve/max-cut") + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(req) + .exchange(); - assertThat(solution.getStatus()) - .isSameAs(SolutionStatus.SOLVED); + response.expectStatus().isOk(); + response.expectBody(new ParameterizedTypeReference>() {}) + .value(Solution::getStatus, is(SOLVED)); } } diff --git a/src/test/java/edu/kit/provideq/toolbox/api/SatSolverTest.java b/src/test/java/edu/kit/provideq/toolbox/api/SatSolverTest.java index e849895d..f32e212d 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/SatSolverTest.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/SatSolverTest.java @@ -1,32 +1,39 @@ package edu.kit.provideq.toolbox.api; -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -import com.fasterxml.jackson.databind.JavaType; -import com.fasterxml.jackson.databind.ObjectMapper; -import edu.kit.provideq.toolbox.Solution; -import edu.kit.provideq.toolbox.SolutionStatus; +import edu.kit.provideq.toolbox.*; +import edu.kit.provideq.toolbox.maxcut.MetaSolverMaxCut; +import edu.kit.provideq.toolbox.maxcut.solvers.GamsMaxCutSolver; +import edu.kit.provideq.toolbox.maxcut.solvers.QiskitMaxCutSolver; +import edu.kit.provideq.toolbox.sat.MetaSolverSat; import edu.kit.provideq.toolbox.sat.SolveSatRequest; import edu.kit.provideq.toolbox.sat.solvers.GamsSatSolver; -import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; +import org.springframework.context.annotation.Import; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.reactive.server.WebTestClient; -@SpringBootTest -@AutoConfigureMockMvc -public class SatSolverTest { - @Autowired - private MockMvc mvc; +import java.util.stream.Stream; +import static edu.kit.provideq.toolbox.SolutionStatus.SOLVED; +import static org.hamcrest.Matchers.is; + +@WebFluxTest +@Import(value = { + SolveRouter.class, + MetaSolverProvider.class, + MetaSolverSat.class, + GamsSatSolver.class, + SubRoutinePool.class, + GamsProcessRunner.class, + ResourceProvider.class +}) +public class SatSolverTest { @Autowired - private ObjectMapper mapper; + private WebTestClient client; public static Stream provideSatSolverIds() { return Stream.of( @@ -36,27 +43,19 @@ public static Stream provideSatSolverIds() { @ParameterizedTest @MethodSource("provideSatSolverIds") - void testSatSolver(String solverId) throws Exception { + void testSatSolver(String solverId) { var req = new SolveSatRequest(); req.requestedSolverId = solverId; req.requestContent = "a and b"; - var requestBuilder = MockMvcRequestBuilders - .post("/solve/sat") - .accept(MediaType.APPLICATION_JSON) - .contentType(MediaType.APPLICATION_JSON) - .content(mapper.writeValueAsString(req)); - - var result = mvc.perform(requestBuilder) - .andExpect(status().isOk()) - .andReturn() - .getResponse().getContentAsString(); - - JavaType solutionType = - mapper.getTypeFactory().constructParametricType(Solution.class, String.class); - Solution solution = mapper.readValue(result, solutionType); + var response = client.post() + .uri("/solve/sat") + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(req) + .exchange(); - assertThat(solution.getStatus()) - .isSameAs(SolutionStatus.SOLVED); + response.expectStatus().isOk(); + response.expectBody(new ParameterizedTypeReference>() {}) + .value(Solution::getStatus, is(SOLVED)); } } From 000034d8bcda0eb6235064aebdac8e4606892af6 Mon Sep 17 00:00:00 2001 From: Max Schweikart Date: Sun, 30 Jul 2023 23:52:13 +0200 Subject: [PATCH 11/22] style: reformat code --- .../provideq/toolbox/MetaSolverProvider.java | 1 - .../kit/provideq/toolbox/api/SolveRouter.java | 432 ++++++++++-------- .../kit/provideq/toolbox/meta/MetaSolver.java | 10 +- .../api/FeatureModelAnomalySolverTest.java | 31 +- .../toolbox/api/MaxCutSolversTest.java | 53 +-- .../provideq/toolbox/api/SatSolverTest.java | 47 +- 6 files changed, 298 insertions(+), 276 deletions(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/MetaSolverProvider.java b/src/main/java/edu/kit/provideq/toolbox/MetaSolverProvider.java index ec8b13e1..95cb9bee 100644 --- a/src/main/java/edu/kit/provideq/toolbox/MetaSolverProvider.java +++ b/src/main/java/edu/kit/provideq/toolbox/MetaSolverProvider.java @@ -2,7 +2,6 @@ import edu.kit.provideq.toolbox.meta.MetaSolver; import edu.kit.provideq.toolbox.meta.ProblemType; - import java.util.Collection; import java.util.Collections; import java.util.Map; diff --git a/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java b/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java index db270781..ae2a8cb9 100644 --- a/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java +++ b/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java @@ -1,6 +1,22 @@ package edu.kit.provideq.toolbox.api; -import edu.kit.provideq.toolbox.*; +import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder; +import static org.springdoc.core.fn.builders.arrayschema.Builder.arraySchemaBuilder; +import static org.springdoc.core.fn.builders.content.Builder.contentBuilder; +import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder; +import static org.springdoc.core.fn.builders.requestbody.Builder.requestBodyBuilder; +import static org.springdoc.core.fn.builders.schema.Builder.schemaBuilder; +import static org.springdoc.webflux.core.fn.SpringdocRouteBuilder.route; +import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.web.reactive.function.server.RequestPredicates.accept; +import static org.springframework.web.reactive.function.server.ServerResponse.ok; + +import edu.kit.provideq.toolbox.MetaSolverProvider; +import edu.kit.provideq.toolbox.ProblemSolverInfo; +import edu.kit.provideq.toolbox.Solution; +import edu.kit.provideq.toolbox.SolutionHandle; +import edu.kit.provideq.toolbox.SolveRequest; import edu.kit.provideq.toolbox.meta.MetaSolver; import edu.kit.provideq.toolbox.meta.ProblemSolver; import edu.kit.provideq.toolbox.meta.SubRoutineDefinition; @@ -21,209 +37,219 @@ import org.springframework.web.server.ServerWebInputException; import reactor.core.publisher.Mono; -import java.util.List; - -import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder; -import static org.springdoc.core.fn.builders.arrayschema.Builder.arraySchemaBuilder; -import static org.springdoc.core.fn.builders.content.Builder.contentBuilder; -import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder; -import static org.springdoc.core.fn.builders.requestbody.Builder.requestBodyBuilder; -import static org.springdoc.core.fn.builders.schema.Builder.schemaBuilder; -import static org.springdoc.webflux.core.fn.SpringdocRouteBuilder.route; -import static org.springframework.http.MediaType.APPLICATION_JSON; -import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; -import static org.springframework.web.reactive.function.server.RequestPredicates.accept; -import static org.springframework.web.reactive.function.server.ServerResponse.ok; - @Configuration @EnableWebFlux public class SolveRouter { - private final MetaSolverProvider metaSolverProvider; - private final Validator validator; - - public SolveRouter(MetaSolverProvider metaSolverProvider, Validator validator) { - this.metaSolverProvider = metaSolverProvider; - this.validator = validator; - } - - @Bean - RouterFunction getSolveRoutes() { - return metaSolverProvider.getMetaSolvers().stream() - .map(this::defineRouteForMetaSolver) - .reduce(RouterFunction::and) - .orElseThrow(); // we should always have at least one route or the toolbox is useless - } - - private RouterFunction defineRouteForMetaSolver(MetaSolver metaSolver) { - String problemId = metaSolver.getProblemType().getId(); - return route().POST( - "/solve/" + problemId, - accept(APPLICATION_JSON), - req -> handleRouteForMetaSolver(metaSolver, req), - ops -> ops - .operationId("/solve/" + problemId) - .tag(problemId) - .requestBody(requestBodyBuilder() - .content(contentBuilder() - .schema(schemaBuilder().implementation(metaSolver.getProblemType().getRequestType())) - .mediaType(APPLICATION_JSON_VALUE) - ) - .required(true) - ) - .response(responseBuilder() - .responseCode(String.valueOf(HttpStatus.OK.value())).implementation(SolutionHandle.class) - ) - ).build(); - } - - private Mono handleRouteForMetaSolver(MetaSolver metaSolver, ServerRequest req) { - var solutionMono = req - .bodyToMono(new ParameterizedTypeReference>() {}) - .doOnNext(this::validate) - .map(metaSolver::solve) - .map(Solution::toStringSolution); - return ok().body(solutionMono, new ParameterizedTypeReference<>() {}); - } - private void validate(SolveRequest request) { - Errors errors = new BeanPropertyBindingResult(request, "request"); - validator.validate(request, errors); - if (errors.hasErrors()) { - throw new ServerWebInputException(errors.toString()); - } - } - - @Bean - RouterFunction getSubRoutineRoutes() { - return metaSolverProvider.getMetaSolvers().stream() - .map(this::defineSubRoutineRouteForMetaSolver) - .reduce(RouterFunction::and) - .orElseThrow(); - } - - private RouterFunction defineSubRoutineRouteForMetaSolver(MetaSolver metaSolver) { - String problemId = metaSolver.getProblemType().getId(); - return route().GET( - "/sub-routines/" + problemId, - req -> handleSubRoutineRouteForMetaSolver(metaSolver, req), - ops -> ops - .operationId("/sub-routines/" + problemId) - .parameter(parameterBuilder().in(ParameterIn.QUERY).name("id")) - .tag(problemId) - .response(responseBuilder() - .responseCode(String.valueOf(HttpStatus.OK.value())) - .content(contentBuilder() - .mediaType(APPLICATION_JSON_VALUE) - .array(arraySchemaBuilder().schema(schemaBuilder().implementation(SubRoutineDefinition.class))) - ) - ) - ).build(); - } - - private Mono handleSubRoutineRouteForMetaSolver(MetaSolver metaSolver, ServerRequest req) { - var subroutines = req.queryParam("id") - .flatMap(metaSolver::getSolver) - .map(ProblemSolver::getSubRoutines) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Could not find a solver for this problem with this solver id!")); - - return ok().body(Mono.just(subroutines), new ParameterizedTypeReference<>() {}); - } - - @Bean - RouterFunction getSolversRoutes() { - return metaSolverProvider.getMetaSolvers().stream() - .map(this::defineSolversRouteForMetaSolver) - .reduce(RouterFunction::and) - .orElseThrow(); - } - - private RouterFunction defineSolversRouteForMetaSolver(MetaSolver metaSolver) { - String problemId = metaSolver.getProblemType().getId(); - return route().GET( - "/solvers/" + problemId, - req -> handleSolversRouteForMetaSolver(metaSolver), - ops -> ops - .operationId("/solvers/" + problemId) - .tag(problemId) - .response(responseBuilder() - .responseCode(String.valueOf(HttpStatus.OK.value())) - .content(contentBuilder() - .mediaType(APPLICATION_JSON_VALUE) - .array(arraySchemaBuilder().schema(schemaBuilder().implementation(ProblemSolverInfo.class))) - ) - ) - ).build(); - } - - private Mono handleSolversRouteForMetaSolver(MetaSolver metaSolver) { - var solvers = metaSolver.getAllSolvers().stream() - .map(solver -> new ProblemSolverInfo(solver.getId(), solver.getName())) - .toList(); - - return ok().body(Mono.just(solvers), new ParameterizedTypeReference<>() {}); - } - - @Bean - RouterFunction getMetaSolverSettingsRoutes() { - return metaSolverProvider.getMetaSolvers().stream() - .map(this::defineMetaSolverSettingsRouteForMetaSolver) - .reduce(RouterFunction::and) - .orElseThrow(); - } - - private RouterFunction defineMetaSolverSettingsRouteForMetaSolver(MetaSolver metaSolver) { - String problemId = metaSolver.getProblemType().getId(); - return route().GET( - "/meta-solver/settings/" + problemId, - req -> handleMetaSolverSettingsRouteForMetaSolver(metaSolver), - ops -> ops - .operationId("/meta-solver/settings/" + problemId) - .tag(problemId) - .response(responseBuilder() - .responseCode(String.valueOf(HttpStatus.OK.value())) - .content(contentBuilder() - .mediaType(APPLICATION_JSON_VALUE) - .array(arraySchemaBuilder().schema(schemaBuilder().implementation(MetaSolverSetting.class))) - ) - ) - ).build(); - } - - private Mono handleMetaSolverSettingsRouteForMetaSolver(MetaSolver metaSolver) { - return ok().body(Mono.just(metaSolver.getSettings()), new ParameterizedTypeReference<>() {}); - } - - @Bean - RouterFunction getSolutionRoutes() { - return metaSolverProvider.getMetaSolvers().stream() - .map(this::defineSolutionRouteForMetaSolver) - .reduce(RouterFunction::and) - .orElseThrow(); // we should always have at least one route or the toolbox is useless - } - - private RouterFunction defineSolutionRouteForMetaSolver(MetaSolver metaSolver) { - String problemId = metaSolver.getProblemType().getId(); - return route().GET( - "/solve/" + problemId, - accept(APPLICATION_JSON), - req -> handleSolutionRouteForMetaSolver(metaSolver, req), - ops -> ops - .operationId("/solution/" + problemId) - .tag(problemId) - .parameter(parameterBuilder().in(ParameterIn.QUERY).name("id")) - .response(responseBuilder() - .responseCode(String.valueOf(HttpStatus.OK.value())).implementation(SolutionHandle.class) - ) - ).build(); - } - - private Mono handleSolutionRouteForMetaSolver(MetaSolver metaSolver, ServerRequest req) { - var solution = req.queryParam("id") - .map(Long::parseLong) - .map(solutionId -> metaSolver.getSolutionManager().getSolution(solutionId)) - .map(Solution::toStringSolution) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Could not find a solution for this problem with this solution id!")); - - // yes, solution is of type `Solution`. No idea why `toStringSolution` returns `SolutionHandle` - return ok().body(Mono.just(solution), new ParameterizedTypeReference>() {}); - } + private final MetaSolverProvider metaSolverProvider; + private final Validator validator; + + public SolveRouter(MetaSolverProvider metaSolverProvider, Validator validator) { + this.metaSolverProvider = metaSolverProvider; + this.validator = validator; + } + + @Bean + RouterFunction getSolveRoutes() { + return metaSolverProvider.getMetaSolvers().stream() + .map(this::defineRouteForMetaSolver) + .reduce(RouterFunction::and) + .orElseThrow(); // we should always have at least one route or the toolbox is useless + } + + private RouterFunction defineRouteForMetaSolver(MetaSolver metaSolver) { + String problemId = metaSolver.getProblemType().getId(); + return route().POST( + "/solve/" + problemId, + accept(APPLICATION_JSON), + req -> handleRouteForMetaSolver(metaSolver, req), + ops -> ops + .operationId("/solve/" + problemId) + .tag(problemId) + .requestBody(requestBodyBuilder() + .content(contentBuilder() + .schema(schemaBuilder().implementation( + metaSolver.getProblemType().getRequestType())) + .mediaType(APPLICATION_JSON_VALUE) + ) + .required(true) + ) + .response(responseBuilder() + .responseCode(String.valueOf(HttpStatus.OK.value())) + .implementation(SolutionHandle.class) + ) + ).build(); + } + + private Mono handleRouteForMetaSolver( + MetaSolver metaSolver, ServerRequest req) { + var solutionMono = req + .bodyToMono(new ParameterizedTypeReference>() { + }) + .doOnNext(this::validate) + .map(metaSolver::solve) + .map(Solution::toStringSolution); + return ok().body(solutionMono, new ParameterizedTypeReference<>() { + }); + } + + private void validate(SolveRequest request) { + Errors errors = new BeanPropertyBindingResult(request, "request"); + validator.validate(request, errors); + if (errors.hasErrors()) { + throw new ServerWebInputException(errors.toString()); + } + } + + @Bean + RouterFunction getSubRoutineRoutes() { + return metaSolverProvider.getMetaSolvers().stream() + .map(this::defineSubRoutineRouteForMetaSolver) + .reduce(RouterFunction::and) + .orElseThrow(); + } + + private RouterFunction defineSubRoutineRouteForMetaSolver( + MetaSolver metaSolver) { + String problemId = metaSolver.getProblemType().getId(); + return route().GET( + "/sub-routines/" + problemId, + req -> handleSubRoutineRouteForMetaSolver(metaSolver, req), + ops -> ops + .operationId("/sub-routines/" + problemId) + .parameter(parameterBuilder().in(ParameterIn.QUERY).name("id")) + .tag(problemId) + .response(responseBuilder() + .responseCode(String.valueOf(HttpStatus.OK.value())) + .content(contentBuilder() + .mediaType(APPLICATION_JSON_VALUE) + .array(arraySchemaBuilder().schema( + schemaBuilder().implementation(SubRoutineDefinition.class))) + ) + ) + ).build(); + } + + private Mono handleSubRoutineRouteForMetaSolver(MetaSolver metaSolver, + ServerRequest req) { + var subroutines = req.queryParam("id") + .flatMap(metaSolver::getSolver) + .map(ProblemSolver::getSubRoutines) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, + "Could not find a solver for this problem with this solver id!")); + + return ok().body(Mono.just(subroutines), new ParameterizedTypeReference<>() { + }); + } + + @Bean + RouterFunction getSolversRoutes() { + return metaSolverProvider.getMetaSolvers().stream() + .map(this::defineSolversRouteForMetaSolver) + .reduce(RouterFunction::and) + .orElseThrow(); + } + + private RouterFunction defineSolversRouteForMetaSolver( + MetaSolver metaSolver) { + String problemId = metaSolver.getProblemType().getId(); + return route().GET( + "/solvers/" + problemId, + req -> handleSolversRouteForMetaSolver(metaSolver), + ops -> ops + .operationId("/solvers/" + problemId) + .tag(problemId) + .response(responseBuilder() + .responseCode(String.valueOf(HttpStatus.OK.value())) + .content(contentBuilder() + .mediaType(APPLICATION_JSON_VALUE) + .array(arraySchemaBuilder().schema( + schemaBuilder().implementation(ProblemSolverInfo.class))) + ) + ) + ).build(); + } + + private Mono handleSolversRouteForMetaSolver(MetaSolver metaSolver) { + var solvers = metaSolver.getAllSolvers().stream() + .map(solver -> new ProblemSolverInfo(solver.getId(), solver.getName())) + .toList(); + + return ok().body(Mono.just(solvers), new ParameterizedTypeReference<>() { + }); + } + + @Bean + RouterFunction getMetaSolverSettingsRoutes() { + return metaSolverProvider.getMetaSolvers().stream() + .map(this::defineMetaSolverSettingsRouteForMetaSolver) + .reduce(RouterFunction::and) + .orElseThrow(); + } + + private RouterFunction defineMetaSolverSettingsRouteForMetaSolver( + MetaSolver metaSolver) { + String problemId = metaSolver.getProblemType().getId(); + return route().GET( + "/meta-solver/settings/" + problemId, + req -> handleMetaSolverSettingsRouteForMetaSolver(metaSolver), + ops -> ops + .operationId("/meta-solver/settings/" + problemId) + .tag(problemId) + .response(responseBuilder() + .responseCode(String.valueOf(HttpStatus.OK.value())) + .content(contentBuilder() + .mediaType(APPLICATION_JSON_VALUE) + .array(arraySchemaBuilder().schema( + schemaBuilder().implementation(MetaSolverSetting.class))) + ) + ) + ).build(); + } + + private Mono handleMetaSolverSettingsRouteForMetaSolver( + MetaSolver metaSolver) { + return ok().body(Mono.just(metaSolver.getSettings()), new ParameterizedTypeReference<>() { + }); + } + + @Bean + RouterFunction getSolutionRoutes() { + return metaSolverProvider.getMetaSolvers().stream() + .map(this::defineSolutionRouteForMetaSolver) + .reduce(RouterFunction::and) + .orElseThrow(); // we should always have at least one route or the toolbox is useless + } + + private RouterFunction defineSolutionRouteForMetaSolver( + MetaSolver metaSolver) { + String problemId = metaSolver.getProblemType().getId(); + return route().GET( + "/solve/" + problemId, + accept(APPLICATION_JSON), + req -> handleSolutionRouteForMetaSolver(metaSolver, req), + ops -> ops + .operationId("/solution/" + problemId) + .tag(problemId) + .parameter(parameterBuilder().in(ParameterIn.QUERY).name("id")) + .response(responseBuilder() + .responseCode(String.valueOf(HttpStatus.OK.value())) + .implementation(SolutionHandle.class) + ) + ).build(); + } + + private Mono handleSolutionRouteForMetaSolver(MetaSolver metaSolver, + ServerRequest req) { + var solution = req.queryParam("id") + .map(Long::parseLong) + .map(solutionId -> metaSolver.getSolutionManager().getSolution(solutionId)) + .map(Solution::toStringSolution) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, + "Could not find a solution for this problem with this solution id!")); + + // yes, solution is of type `Solution`. + // No idea why `toStringSolution` returns `SolutionHandle` + return ok().body(Mono.just(solution), new ParameterizedTypeReference>() { + }); + } } diff --git a/src/main/java/edu/kit/provideq/toolbox/meta/MetaSolver.java b/src/main/java/edu/kit/provideq/toolbox/meta/MetaSolver.java index 480da1dc..ac75cd02 100644 --- a/src/main/java/edu/kit/provideq/toolbox/meta/MetaSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/meta/MetaSolver.java @@ -5,18 +5,12 @@ import edu.kit.provideq.toolbox.SolveRequest; import edu.kit.provideq.toolbox.SubRoutinePool; import edu.kit.provideq.toolbox.meta.setting.MetaSolverSetting; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.http.HttpStatus; -import org.springframework.web.reactive.function.server.ServerRequest; -import org.springframework.web.server.ResponseStatusException; -import reactor.core.publisher.Mono; - import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; /** * Decides which known {@link ProblemSolver} is suited best for a given problem, diff --git a/src/test/java/edu/kit/provideq/toolbox/api/FeatureModelAnomalySolverTest.java b/src/test/java/edu/kit/provideq/toolbox/api/FeatureModelAnomalySolverTest.java index 729d1f06..75cb89ce 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/FeatureModelAnomalySolverTest.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/FeatureModelAnomalySolverTest.java @@ -1,5 +1,8 @@ package edu.kit.provideq.toolbox.api; +import static edu.kit.provideq.toolbox.SolutionStatus.SOLVED; +import static org.hamcrest.Matchers.is; + import edu.kit.provideq.toolbox.MetaSolverProvider; import edu.kit.provideq.toolbox.Solution; import edu.kit.provideq.toolbox.SolutionStatus; @@ -7,6 +10,7 @@ import edu.kit.provideq.toolbox.featuremodel.SolveFeatureModelRequest; import edu.kit.provideq.toolbox.featuremodel.anomaly.MetaSolverFeatureModelAnomaly; import edu.kit.provideq.toolbox.featuremodel.anomaly.solvers.FeatureModelAnomalySolver; +import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -17,18 +21,14 @@ import org.springframework.http.MediaType; import org.springframework.test.web.reactive.server.WebTestClient; -import java.util.stream.Stream; - -import static edu.kit.provideq.toolbox.SolutionStatus.SOLVED; -import static org.hamcrest.Matchers.is; @WebFluxTest @Import(value = { - SolveRouter.class, - MetaSolverProvider.class, - MetaSolverFeatureModelAnomaly.class, - FeatureModelAnomalySolver.class, - SubRoutinePool.class + SolveRouter.class, + MetaSolverProvider.class, + MetaSolverFeatureModelAnomaly.class, + FeatureModelAnomalySolver.class, + SubRoutinePool.class }) public class FeatureModelAnomalySolverTest { @Autowired @@ -85,13 +85,14 @@ void testFeatureModelAnomalySolver(String solverId, String anomalyType, """; var response = client.post() - .uri("/solve/feature-model-anomaly/" + anomalyType) // FIXME type of anomaly? - .contentType(MediaType.APPLICATION_JSON) - .bodyValue(req) - .exchange(); + .uri("/solve/feature-model-anomaly/" + anomalyType) // FIXME type of anomaly? + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(req) + .exchange(); response.expectStatus().isOk(); - response.expectBody(new ParameterizedTypeReference>() {}) - .value(Solution::getStatus, is(expectedStatus)); + response.expectBody(new ParameterizedTypeReference>() { + }) + .value(Solution::getStatus, is(expectedStatus)); } } diff --git a/src/test/java/edu/kit/provideq/toolbox/api/MaxCutSolversTest.java b/src/test/java/edu/kit/provideq/toolbox/api/MaxCutSolversTest.java index 8e86ec9a..88716fc0 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/MaxCutSolversTest.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/MaxCutSolversTest.java @@ -1,40 +1,40 @@ package edu.kit.provideq.toolbox.api; -import edu.kit.provideq.toolbox.*; +import static edu.kit.provideq.toolbox.SolutionStatus.SOLVED; +import static org.hamcrest.Matchers.is; + +import edu.kit.provideq.toolbox.GamsProcessRunner; +import edu.kit.provideq.toolbox.MetaSolverProvider; +import edu.kit.provideq.toolbox.PythonProcessRunner; +import edu.kit.provideq.toolbox.ResourceProvider; +import edu.kit.provideq.toolbox.Solution; +import edu.kit.provideq.toolbox.SubRoutinePool; import edu.kit.provideq.toolbox.maxcut.MetaSolverMaxCut; import edu.kit.provideq.toolbox.maxcut.SolveMaxCutRequest; import edu.kit.provideq.toolbox.maxcut.solvers.GamsMaxCutSolver; import edu.kit.provideq.toolbox.maxcut.solvers.QiskitMaxCutSolver; -import org.junit.jupiter.api.BeforeEach; +import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.MediaType; import org.springframework.test.web.reactive.server.WebTestClient; -import java.util.stream.Stream; - -import static edu.kit.provideq.toolbox.SolutionStatus.SOLVED; -import static org.hamcrest.Matchers.is; - @WebFluxTest @Import(value = { - SolveRouter.class, - MetaSolverProvider.class, - MetaSolverMaxCut.class, - QiskitMaxCutSolver.class, - GamsMaxCutSolver.class, - QiskitMaxCutSolver.class, - SubRoutinePool.class, - GamsProcessRunner.class, - PythonProcessRunner.class, - ResourceProvider.class + SolveRouter.class, + MetaSolverProvider.class, + MetaSolverMaxCut.class, + QiskitMaxCutSolver.class, + GamsMaxCutSolver.class, + QiskitMaxCutSolver.class, + SubRoutinePool.class, + GamsProcessRunner.class, + PythonProcessRunner.class, + ResourceProvider.class }) class MaxCutSolversTest { @Autowired @@ -82,13 +82,14 @@ void testMaxCutSolver(String solverId) { ]"""; var response = client.post() - .uri("/solve/max-cut") - .contentType(MediaType.APPLICATION_JSON) - .bodyValue(req) - .exchange(); + .uri("/solve/max-cut") + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(req) + .exchange(); response.expectStatus().isOk(); - response.expectBody(new ParameterizedTypeReference>() {}) - .value(Solution::getStatus, is(SOLVED)); + response.expectBody(new ParameterizedTypeReference>() { + }) + .value(Solution::getStatus, is(SOLVED)); } } diff --git a/src/test/java/edu/kit/provideq/toolbox/api/SatSolverTest.java b/src/test/java/edu/kit/provideq/toolbox/api/SatSolverTest.java index f32e212d..c54059d5 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/SatSolverTest.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/SatSolverTest.java @@ -1,12 +1,17 @@ package edu.kit.provideq.toolbox.api; -import edu.kit.provideq.toolbox.*; -import edu.kit.provideq.toolbox.maxcut.MetaSolverMaxCut; -import edu.kit.provideq.toolbox.maxcut.solvers.GamsMaxCutSolver; -import edu.kit.provideq.toolbox.maxcut.solvers.QiskitMaxCutSolver; +import static edu.kit.provideq.toolbox.SolutionStatus.SOLVED; +import static org.hamcrest.Matchers.is; + +import edu.kit.provideq.toolbox.GamsProcessRunner; +import edu.kit.provideq.toolbox.MetaSolverProvider; +import edu.kit.provideq.toolbox.ResourceProvider; +import edu.kit.provideq.toolbox.Solution; +import edu.kit.provideq.toolbox.SubRoutinePool; import edu.kit.provideq.toolbox.sat.MetaSolverSat; import edu.kit.provideq.toolbox.sat.SolveSatRequest; import edu.kit.provideq.toolbox.sat.solvers.GamsSatSolver; +import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.springframework.beans.factory.annotation.Autowired; @@ -16,26 +21,21 @@ import org.springframework.http.MediaType; import org.springframework.test.web.reactive.server.WebTestClient; -import java.util.stream.Stream; - -import static edu.kit.provideq.toolbox.SolutionStatus.SOLVED; -import static org.hamcrest.Matchers.is; - @WebFluxTest @Import(value = { - SolveRouter.class, - MetaSolverProvider.class, - MetaSolverSat.class, - GamsSatSolver.class, - SubRoutinePool.class, - GamsProcessRunner.class, - ResourceProvider.class + SolveRouter.class, + MetaSolverProvider.class, + MetaSolverSat.class, + GamsSatSolver.class, + SubRoutinePool.class, + GamsProcessRunner.class, + ResourceProvider.class }) public class SatSolverTest { @Autowired private WebTestClient client; - public static Stream provideSatSolverIds() { + static Stream provideSatSolverIds() { return Stream.of( GamsSatSolver.class.getName() ); @@ -49,13 +49,14 @@ void testSatSolver(String solverId) { req.requestContent = "a and b"; var response = client.post() - .uri("/solve/sat") - .contentType(MediaType.APPLICATION_JSON) - .bodyValue(req) - .exchange(); + .uri("/solve/sat") + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(req) + .exchange(); response.expectStatus().isOk(); - response.expectBody(new ParameterizedTypeReference>() {}) - .value(Solution::getStatus, is(SOLVED)); + response.expectBody(new ParameterizedTypeReference>() { + }) + .value(Solution::getStatus, is(SOLVED)); } } From 1b35f8e5a3abd790725cefc81dceedc5e91f7d85 Mon Sep 17 00:00:00 2001 From: Max Schweikart Date: Tue, 1 Aug 2023 14:35:50 +0200 Subject: [PATCH 12/22] chore: ignore further GAMS output files --- .gitignore | 1 + a.out | 0 2 files changed, 1 insertion(+) delete mode 100644 a.out diff --git a/.gitignore b/.gitignore index ca4eeedb..61330eb1 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,4 @@ gamslice.txt *.lst *.op2 gams/**/225a +*.out diff --git a/a.out b/a.out deleted file mode 100644 index e69de29b..00000000 From 3c5d08e80b9f9556d299084f88c19879ff004b6e Mon Sep 17 00:00:00 2001 From: Max Schweikart Date: Tue, 1 Aug 2023 15:21:13 +0200 Subject: [PATCH 13/22] docs: add javadocs to routing components --- .../provideq/toolbox/MetaSolverProvider.java | 18 ++++++++ .../kit/provideq/toolbox/meta/MetaSolver.java | 44 +++++++------------ .../provideq/toolbox/meta/ProblemType.java | 6 +++ .../api/FeatureModelAnomalySolverTest.java | 4 +- .../toolbox/api/MaxCutSolversTest.java | 2 +- .../provideq/toolbox/api/SatSolverTest.java | 2 +- 6 files changed, 43 insertions(+), 33 deletions(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/MetaSolverProvider.java b/src/main/java/edu/kit/provideq/toolbox/MetaSolverProvider.java index 95cb9bee..05b8cd96 100644 --- a/src/main/java/edu/kit/provideq/toolbox/MetaSolverProvider.java +++ b/src/main/java/edu/kit/provideq/toolbox/MetaSolverProvider.java @@ -10,10 +10,19 @@ import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component; +/** + * The meta solver provide can be used to find all or specific {@link MetaSolver}s. + */ @Component public class MetaSolverProvider { private final Map> metaSolvers; + /** + * Initializes a meta solver provider bean. + * This provider will fetch available meta solvers once through the given {@code context}. + * + * @param context used to find available meta solvers. + */ @Autowired public MetaSolverProvider(ApplicationContext context) { this.metaSolvers = @@ -25,10 +34,19 @@ public MetaSolverProvider(ApplicationContext context) { metaSolver -> (MetaSolver) metaSolver)); } + /** + * Finds a meta solver for the given problem type. + * + * @param problemType the type of problem to find the meta solver of. + * @return the meta solver that manages solvers of the given type. + */ public MetaSolver getMetaSolver(ProblemType problemType) { return metaSolvers.get(problemType); } + /** + * Returns all registered meta solvers for all available problem types. + */ public Collection> getMetaSolvers() { return Collections.unmodifiableCollection(metaSolvers.values()); } diff --git a/src/main/java/edu/kit/provideq/toolbox/meta/MetaSolver.java b/src/main/java/edu/kit/provideq/toolbox/meta/MetaSolver.java index ac75cd02..ab6be8b9 100644 --- a/src/main/java/edu/kit/provideq/toolbox/meta/MetaSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/meta/MetaSolver.java @@ -29,16 +29,12 @@ public abstract class MetaSolver< private ApplicationContext context; protected Set solvers = new HashSet<>(); - private ProblemType problemType; - - public MetaSolver() { - } - - public MetaSolver(ProblemType problemType, List problemSolvers) { - solvers.addAll(problemSolvers); - this.problemType = problemType; - } + private final ProblemType problemType; + /** + * Configures this meta solver to find the correct solver among {@code problemSolvers}, all of + * which solve problems of type {@code problemType}. + */ @SafeVarargs public MetaSolver(ProblemType problemType, SolverT... problemSolvers) { solvers.addAll(List.of(problemSolvers)); @@ -50,26 +46,6 @@ public void setContext(ApplicationContext context) { this.context = context; } - /** - * Adds a new solver to this meta solvers list of known solvers. - * - * @param problemSolver the new problem solver - * @return true in case the addition was successful, false otherwise - */ - public boolean registerSolver(SolverT problemSolver) { - return solvers.add(problemSolver); - } - - /** - * Removes a solver from this meta solvers list of known solvers. - * - * @param problemSolver the solver - * @return true in case the removal was successful, false otherwise - */ - public boolean unregisterSolver(SolverT problemSolver) { - return solvers.remove(problemSolver); - } - /** * Provides the best suited known solver this meta solver is aware of for a given problem. * @@ -80,6 +56,11 @@ public abstract SolverT findSolver( Problem problem, List metaSolverSettings); + /** + * Returns the solver from {@link #getAllSolvers()} with the given {@code solverId}. + * The optional is empty if there is no solver with the given {@code solverId} known by this + * meta-solver. + */ public Optional getSolver(String solverId) { if (solverId == null) { return Optional.empty(); @@ -116,6 +97,11 @@ public SolutionManager getSolutionManager() { return solutionManager; } + /** + * Solves a given {@link SolveRequest} by using either the requested {@link ProblemSolver} + * (if specified) or the solver recommended by {@link #findSolver(Problem, List)}, and returns + * the solution. + */ public Solution solve(SolveRequest request) { Solution solution = this.getSolutionManager().createSolution(); Problem problem = new Problem<>(request.requestContent, this.getProblemType()); diff --git a/src/main/java/edu/kit/provideq/toolbox/meta/ProblemType.java b/src/main/java/edu/kit/provideq/toolbox/meta/ProblemType.java index b7a149d7..e904bed3 100644 --- a/src/main/java/edu/kit/provideq/toolbox/meta/ProblemType.java +++ b/src/main/java/edu/kit/provideq/toolbox/meta/ProblemType.java @@ -39,10 +39,16 @@ public enum ProblemType { this.requestType = requestType; } + /** + * Returns a unique identifier for this problem type. + */ public String getId() { return id; } + /** + * Returns the java class representing the body of a REST request to solve a problem of this type. + */ public Class> getRequestType() { return requestType; } diff --git a/src/test/java/edu/kit/provideq/toolbox/api/FeatureModelAnomalySolverTest.java b/src/test/java/edu/kit/provideq/toolbox/api/FeatureModelAnomalySolverTest.java index 75cb89ce..914e1650 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/FeatureModelAnomalySolverTest.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/FeatureModelAnomalySolverTest.java @@ -30,11 +30,11 @@ FeatureModelAnomalySolver.class, SubRoutinePool.class }) -public class FeatureModelAnomalySolverTest { +class FeatureModelAnomalySolverTest { @Autowired private WebTestClient client; - public static Stream provideAnomalySolverIds() { + static Stream provideAnomalySolverIds() { String solverId = FeatureModelAnomalySolver.class.getName(); return Stream.of( Arguments.of(solverId, "void", SOLVED), diff --git a/src/test/java/edu/kit/provideq/toolbox/api/MaxCutSolversTest.java b/src/test/java/edu/kit/provideq/toolbox/api/MaxCutSolversTest.java index 88716fc0..342b1bfb 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/MaxCutSolversTest.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/MaxCutSolversTest.java @@ -40,7 +40,7 @@ class MaxCutSolversTest { @Autowired private WebTestClient client; - public static Stream provideMaxCutSolverIds() { + static Stream provideMaxCutSolverIds() { return Stream.of( GamsMaxCutSolver.class.getName(), QiskitMaxCutSolver.class.getName() diff --git a/src/test/java/edu/kit/provideq/toolbox/api/SatSolverTest.java b/src/test/java/edu/kit/provideq/toolbox/api/SatSolverTest.java index c54059d5..38d1adb0 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/SatSolverTest.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/SatSolverTest.java @@ -31,7 +31,7 @@ GamsProcessRunner.class, ResourceProvider.class }) -public class SatSolverTest { +class SatSolverTest { @Autowired private WebTestClient client; From baa581bbaad52eda8e8495a443ead692d3bb3434 Mon Sep 17 00:00:00 2001 From: Max Schweikart Date: Tue, 1 Aug 2023 15:39:55 +0200 Subject: [PATCH 14/22] refactor: split up router by endpoint --- .../toolbox/api/MetaSolverSettingsRouter.java | 72 +++++++++++ .../kit/provideq/toolbox/api/SolveRouter.java | 122 +----------------- .../provideq/toolbox/api/SolversRouter.java | 76 +++++++++++ .../toolbox/api/SubRoutineRouter.java | 85 ++++++++++++ 4 files changed, 238 insertions(+), 117 deletions(-) create mode 100644 src/main/java/edu/kit/provideq/toolbox/api/MetaSolverSettingsRouter.java create mode 100644 src/main/java/edu/kit/provideq/toolbox/api/SolversRouter.java create mode 100644 src/main/java/edu/kit/provideq/toolbox/api/SubRoutineRouter.java diff --git a/src/main/java/edu/kit/provideq/toolbox/api/MetaSolverSettingsRouter.java b/src/main/java/edu/kit/provideq/toolbox/api/MetaSolverSettingsRouter.java new file mode 100644 index 00000000..593828c4 --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/api/MetaSolverSettingsRouter.java @@ -0,0 +1,72 @@ +package edu.kit.provideq.toolbox.api; + +import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder; +import static org.springdoc.core.fn.builders.arrayschema.Builder.arraySchemaBuilder; +import static org.springdoc.core.fn.builders.content.Builder.contentBuilder; +import static org.springdoc.core.fn.builders.schema.Builder.schemaBuilder; +import static org.springdoc.webflux.core.fn.SpringdocRouteBuilder.route; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.web.reactive.function.server.ServerResponse.ok; + +import edu.kit.provideq.toolbox.MetaSolverProvider; +import edu.kit.provideq.toolbox.meta.MetaSolver; +import edu.kit.provideq.toolbox.meta.setting.MetaSolverSetting; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; +import org.springframework.web.reactive.config.EnableWebFlux; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.ServerResponse; +import reactor.core.publisher.Mono; + +/** + * This router handles requests to the GET {@code /meta-solver/{problemType}/settings} endpoints. + * Responses are generated from the settings reported by the available meta-solvers + * (see {@link MetaSolver#getSettings()}). + */ +@Configuration +@EnableWebFlux +public class MetaSolverSettingsRouter { + private final MetaSolverProvider metaSolverProvider; + + @Autowired + public MetaSolverSettingsRouter(MetaSolverProvider metaSolverProvider) { + this.metaSolverProvider = metaSolverProvider; + } + + @Bean + RouterFunction getMetaSolverSettingsRoutes() { + return metaSolverProvider.getMetaSolvers().stream() + .map(this::defineMetaSolverSettingsRouteForMetaSolver) + .reduce(RouterFunction::and) + .orElseThrow(); + } + + private RouterFunction defineMetaSolverSettingsRouteForMetaSolver( + MetaSolver metaSolver) { + String problemId = metaSolver.getProblemType().getId(); + return route().GET( + "/meta-solver/settings/" + problemId, + req -> handleMetaSolverSettingsRouteForMetaSolver(metaSolver), + ops -> ops + .operationId("/meta-solver/settings/" + problemId) + .tag(problemId) + .response(responseBuilder() + .responseCode(String.valueOf(HttpStatus.OK.value())) + .content(contentBuilder() + .mediaType(APPLICATION_JSON_VALUE) + .array(arraySchemaBuilder().schema( + schemaBuilder().implementation(MetaSolverSetting.class))) + ) + ) + ).build(); + } + + private Mono handleMetaSolverSettingsRouteForMetaSolver( + MetaSolver metaSolver) { + return ok().body(Mono.just(metaSolver.getSettings()), new ParameterizedTypeReference<>() { + }); + } +} diff --git a/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java b/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java index ae2a8cb9..446888cd 100644 --- a/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java +++ b/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java @@ -1,7 +1,6 @@ package edu.kit.provideq.toolbox.api; import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder; -import static org.springdoc.core.fn.builders.arrayschema.Builder.arraySchemaBuilder; import static org.springdoc.core.fn.builders.content.Builder.contentBuilder; import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder; import static org.springdoc.core.fn.builders.requestbody.Builder.requestBodyBuilder; @@ -13,14 +12,10 @@ import static org.springframework.web.reactive.function.server.ServerResponse.ok; import edu.kit.provideq.toolbox.MetaSolverProvider; -import edu.kit.provideq.toolbox.ProblemSolverInfo; import edu.kit.provideq.toolbox.Solution; import edu.kit.provideq.toolbox.SolutionHandle; import edu.kit.provideq.toolbox.SolveRequest; import edu.kit.provideq.toolbox.meta.MetaSolver; -import edu.kit.provideq.toolbox.meta.ProblemSolver; -import edu.kit.provideq.toolbox.meta.SubRoutineDefinition; -import edu.kit.provideq.toolbox.meta.setting.MetaSolverSetting; import io.swagger.v3.oas.annotations.enums.ParameterIn; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -37,6 +32,11 @@ import org.springframework.web.server.ServerWebInputException; import reactor.core.publisher.Mono; +/** + * This router handles problem-solving requests to the GET and POST {@code /solve/{problemType}} + * endpoints. + * Requests are validated and relayed to the corresponding {@link MetaSolver}. + */ @Configuration @EnableWebFlux public class SolveRouter { @@ -100,118 +100,6 @@ private void validate(SolveRequest request) { } } - @Bean - RouterFunction getSubRoutineRoutes() { - return metaSolverProvider.getMetaSolvers().stream() - .map(this::defineSubRoutineRouteForMetaSolver) - .reduce(RouterFunction::and) - .orElseThrow(); - } - - private RouterFunction defineSubRoutineRouteForMetaSolver( - MetaSolver metaSolver) { - String problemId = metaSolver.getProblemType().getId(); - return route().GET( - "/sub-routines/" + problemId, - req -> handleSubRoutineRouteForMetaSolver(metaSolver, req), - ops -> ops - .operationId("/sub-routines/" + problemId) - .parameter(parameterBuilder().in(ParameterIn.QUERY).name("id")) - .tag(problemId) - .response(responseBuilder() - .responseCode(String.valueOf(HttpStatus.OK.value())) - .content(contentBuilder() - .mediaType(APPLICATION_JSON_VALUE) - .array(arraySchemaBuilder().schema( - schemaBuilder().implementation(SubRoutineDefinition.class))) - ) - ) - ).build(); - } - - private Mono handleSubRoutineRouteForMetaSolver(MetaSolver metaSolver, - ServerRequest req) { - var subroutines = req.queryParam("id") - .flatMap(metaSolver::getSolver) - .map(ProblemSolver::getSubRoutines) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, - "Could not find a solver for this problem with this solver id!")); - - return ok().body(Mono.just(subroutines), new ParameterizedTypeReference<>() { - }); - } - - @Bean - RouterFunction getSolversRoutes() { - return metaSolverProvider.getMetaSolvers().stream() - .map(this::defineSolversRouteForMetaSolver) - .reduce(RouterFunction::and) - .orElseThrow(); - } - - private RouterFunction defineSolversRouteForMetaSolver( - MetaSolver metaSolver) { - String problemId = metaSolver.getProblemType().getId(); - return route().GET( - "/solvers/" + problemId, - req -> handleSolversRouteForMetaSolver(metaSolver), - ops -> ops - .operationId("/solvers/" + problemId) - .tag(problemId) - .response(responseBuilder() - .responseCode(String.valueOf(HttpStatus.OK.value())) - .content(contentBuilder() - .mediaType(APPLICATION_JSON_VALUE) - .array(arraySchemaBuilder().schema( - schemaBuilder().implementation(ProblemSolverInfo.class))) - ) - ) - ).build(); - } - - private Mono handleSolversRouteForMetaSolver(MetaSolver metaSolver) { - var solvers = metaSolver.getAllSolvers().stream() - .map(solver -> new ProblemSolverInfo(solver.getId(), solver.getName())) - .toList(); - - return ok().body(Mono.just(solvers), new ParameterizedTypeReference<>() { - }); - } - - @Bean - RouterFunction getMetaSolverSettingsRoutes() { - return metaSolverProvider.getMetaSolvers().stream() - .map(this::defineMetaSolverSettingsRouteForMetaSolver) - .reduce(RouterFunction::and) - .orElseThrow(); - } - - private RouterFunction defineMetaSolverSettingsRouteForMetaSolver( - MetaSolver metaSolver) { - String problemId = metaSolver.getProblemType().getId(); - return route().GET( - "/meta-solver/settings/" + problemId, - req -> handleMetaSolverSettingsRouteForMetaSolver(metaSolver), - ops -> ops - .operationId("/meta-solver/settings/" + problemId) - .tag(problemId) - .response(responseBuilder() - .responseCode(String.valueOf(HttpStatus.OK.value())) - .content(contentBuilder() - .mediaType(APPLICATION_JSON_VALUE) - .array(arraySchemaBuilder().schema( - schemaBuilder().implementation(MetaSolverSetting.class))) - ) - ) - ).build(); - } - - private Mono handleMetaSolverSettingsRouteForMetaSolver( - MetaSolver metaSolver) { - return ok().body(Mono.just(metaSolver.getSettings()), new ParameterizedTypeReference<>() { - }); - } - @Bean RouterFunction getSolutionRoutes() { return metaSolverProvider.getMetaSolvers().stream() diff --git a/src/main/java/edu/kit/provideq/toolbox/api/SolversRouter.java b/src/main/java/edu/kit/provideq/toolbox/api/SolversRouter.java new file mode 100644 index 00000000..07db7979 --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/api/SolversRouter.java @@ -0,0 +1,76 @@ +package edu.kit.provideq.toolbox.api; + +import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder; +import static org.springdoc.core.fn.builders.arrayschema.Builder.arraySchemaBuilder; +import static org.springdoc.core.fn.builders.content.Builder.contentBuilder; +import static org.springdoc.core.fn.builders.schema.Builder.schemaBuilder; +import static org.springdoc.webflux.core.fn.SpringdocRouteBuilder.route; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.web.reactive.function.server.ServerResponse.ok; + +import edu.kit.provideq.toolbox.MetaSolverProvider; +import edu.kit.provideq.toolbox.ProblemSolverInfo; +import edu.kit.provideq.toolbox.meta.MetaSolver; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; +import org.springframework.web.reactive.config.EnableWebFlux; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.ServerResponse; +import reactor.core.publisher.Mono; + +/** + * This router handles solver discovery requests to the GET {@code /solvers/{problemType}} + * endpoints. + * Responses are generated from the solvers reported by the meta-solver registered for the given + * problem type. + */ +@Configuration +@EnableWebFlux +public class SolversRouter { + private final MetaSolverProvider metaSolverProvider; + + @Autowired + public SolversRouter(MetaSolverProvider metaSolverProvider) { + this.metaSolverProvider = metaSolverProvider; + } + + @Bean + RouterFunction getSolversRoutes() { + return metaSolverProvider.getMetaSolvers().stream() + .map(this::defineSolversRouteForMetaSolver) + .reduce(RouterFunction::and) + .orElseThrow(); + } + + private RouterFunction defineSolversRouteForMetaSolver( + MetaSolver metaSolver) { + String problemId = metaSolver.getProblemType().getId(); + return route().GET( + "/solvers/" + problemId, + req -> handleSolversRouteForMetaSolver(metaSolver), + ops -> ops + .operationId("/solvers/" + problemId) + .tag(problemId) + .response(responseBuilder() + .responseCode(String.valueOf(HttpStatus.OK.value())) + .content(contentBuilder() + .mediaType(APPLICATION_JSON_VALUE) + .array(arraySchemaBuilder().schema( + schemaBuilder().implementation(ProblemSolverInfo.class))) + ) + ) + ).build(); + } + + private Mono handleSolversRouteForMetaSolver(MetaSolver metaSolver) { + var solvers = metaSolver.getAllSolvers().stream() + .map(solver -> new ProblemSolverInfo(solver.getId(), solver.getName())) + .toList(); + + return ok().body(Mono.just(solvers), new ParameterizedTypeReference<>() { + }); + } +} diff --git a/src/main/java/edu/kit/provideq/toolbox/api/SubRoutineRouter.java b/src/main/java/edu/kit/provideq/toolbox/api/SubRoutineRouter.java new file mode 100644 index 00000000..8683c63e --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/api/SubRoutineRouter.java @@ -0,0 +1,85 @@ +package edu.kit.provideq.toolbox.api; + +import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder; +import static org.springdoc.core.fn.builders.arrayschema.Builder.arraySchemaBuilder; +import static org.springdoc.core.fn.builders.content.Builder.contentBuilder; +import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder; +import static org.springdoc.core.fn.builders.schema.Builder.schemaBuilder; +import static org.springdoc.webflux.core.fn.SpringdocRouteBuilder.route; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.web.reactive.function.server.ServerResponse.ok; + +import edu.kit.provideq.toolbox.MetaSolverProvider; +import edu.kit.provideq.toolbox.meta.MetaSolver; +import edu.kit.provideq.toolbox.meta.ProblemSolver; +import edu.kit.provideq.toolbox.meta.SubRoutineDefinition; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpStatus; +import org.springframework.web.reactive.config.EnableWebFlux; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.reactive.function.server.ServerResponse; +import org.springframework.web.server.ResponseStatusException; +import reactor.core.publisher.Mono; + +/** + * This router handles sub-routine discovery requests to the GET {@code /sub-routines/{problemType}} + * endpoints. + * Responses are generated from the sub-routine data reported by the given solver itself + * (see {@link ProblemSolver#getSubRoutines()}). + */ +@Configuration +@EnableWebFlux +public class SubRoutineRouter { + private final MetaSolverProvider metaSolverProvider; + + @Autowired + public SubRoutineRouter(MetaSolverProvider metaSolverProvider) { + this.metaSolverProvider = metaSolverProvider; + } + + @Bean + RouterFunction getSubRoutineRoutes() { + return metaSolverProvider.getMetaSolvers().stream() + .map(this::defineSubRoutineRouteForMetaSolver) + .reduce(RouterFunction::and) + .orElseThrow(); + } + + private RouterFunction defineSubRoutineRouteForMetaSolver( + MetaSolver metaSolver) { + String problemId = metaSolver.getProblemType().getId(); + return route().GET( + "/sub-routines/" + problemId, + req -> handleSubRoutineRouteForMetaSolver(metaSolver, req), + ops -> ops + .operationId("/sub-routines/" + problemId) + .parameter(parameterBuilder().in(ParameterIn.QUERY).name("id")) + .tag(problemId) + .response(responseBuilder() + .responseCode(String.valueOf(HttpStatus.OK.value())) + .content(contentBuilder() + .mediaType(APPLICATION_JSON_VALUE) + .array(arraySchemaBuilder().schema( + schemaBuilder().implementation(SubRoutineDefinition.class))) + ) + ) + ).build(); + } + + private Mono handleSubRoutineRouteForMetaSolver(MetaSolver metaSolver, + ServerRequest req) { + var subroutines = req.queryParam("id") + .flatMap(metaSolver::getSolver) + .map(ProblemSolver::getSubRoutines) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, + "Could not find a solver for this problem with this solver id!")); + + return ok().body(Mono.just(subroutines), new ParameterizedTypeReference<>() { + }); + } +} From 59967346da300228b7ce0c8c12fe0586b63973e8 Mon Sep 17 00:00:00 2001 From: Max Schweikart Date: Tue, 1 Aug 2023 16:31:14 +0200 Subject: [PATCH 15/22] fix: extend test duration --- .../edu/kit/provideq/toolbox/api/MaxCutSolversTest.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/test/java/edu/kit/provideq/toolbox/api/MaxCutSolversTest.java b/src/test/java/edu/kit/provideq/toolbox/api/MaxCutSolversTest.java index 342b1bfb..b64da41a 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/MaxCutSolversTest.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/MaxCutSolversTest.java @@ -13,7 +13,9 @@ import edu.kit.provideq.toolbox.maxcut.SolveMaxCutRequest; import edu.kit.provideq.toolbox.maxcut.solvers.GamsMaxCutSolver; import edu.kit.provideq.toolbox.maxcut.solvers.QiskitMaxCutSolver; +import java.time.Duration; import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.springframework.beans.factory.annotation.Autowired; @@ -40,6 +42,13 @@ class MaxCutSolversTest { @Autowired private WebTestClient client; + @BeforeEach + void beforeEach() { + this.client = this.client.mutate() + .responseTimeout(Duration.ofSeconds(10)) + .build(); + } + static Stream provideMaxCutSolverIds() { return Stream.of( GamsMaxCutSolver.class.getName(), From f59193e937494dbfbc7e2c4bc73e6222305ac119 Mon Sep 17 00:00:00 2001 From: Max Schweikart Date: Sat, 2 Sep 2023 21:49:25 +0200 Subject: [PATCH 16/22] refactor: de-duplicate route names in router code --- .../toolbox/api/MetaSolverSettingsRouter.java | 13 ++++++--- .../kit/provideq/toolbox/api/SolveRouter.java | 27 +++++++++++++------ .../provideq/toolbox/api/SolversRouter.java | 13 ++++++--- .../toolbox/api/SubRoutineRouter.java | 13 ++++++--- 4 files changed, 46 insertions(+), 20 deletions(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/api/MetaSolverSettingsRouter.java b/src/main/java/edu/kit/provideq/toolbox/api/MetaSolverSettingsRouter.java index 593828c4..db4304a7 100644 --- a/src/main/java/edu/kit/provideq/toolbox/api/MetaSolverSettingsRouter.java +++ b/src/main/java/edu/kit/provideq/toolbox/api/MetaSolverSettingsRouter.java @@ -10,6 +10,7 @@ import edu.kit.provideq.toolbox.MetaSolverProvider; import edu.kit.provideq.toolbox.meta.MetaSolver; +import edu.kit.provideq.toolbox.meta.ProblemType; import edu.kit.provideq.toolbox.meta.setting.MetaSolverSetting; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; @@ -46,13 +47,13 @@ RouterFunction getMetaSolverSettingsRoutes() { private RouterFunction defineMetaSolverSettingsRouteForMetaSolver( MetaSolver metaSolver) { - String problemId = metaSolver.getProblemType().getId(); + var problemType = metaSolver.getProblemType(); return route().GET( - "/meta-solver/settings/" + problemId, + getRouteForProblemType(problemType), req -> handleMetaSolverSettingsRouteForMetaSolver(metaSolver), ops -> ops - .operationId("/meta-solver/settings/" + problemId) - .tag(problemId) + .operationId(getRouteForProblemType(problemType)) + .tag(problemType.getId()) .response(responseBuilder() .responseCode(String.valueOf(HttpStatus.OK.value())) .content(contentBuilder() @@ -69,4 +70,8 @@ private Mono handleMetaSolverSettingsRouteForMetaSolver( return ok().body(Mono.just(metaSolver.getSettings()), new ParameterizedTypeReference<>() { }); } + + private String getRouteForProblemType(ProblemType type) { + return "/meta-solver/settings/" + type.getId(); + } } diff --git a/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java b/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java index 446888cd..c7e60b52 100644 --- a/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java +++ b/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java @@ -16,6 +16,7 @@ import edu.kit.provideq.toolbox.SolutionHandle; import edu.kit.provideq.toolbox.SolveRequest; import edu.kit.provideq.toolbox.meta.MetaSolver; +import edu.kit.provideq.toolbox.meta.ProblemType; import io.swagger.v3.oas.annotations.enums.ParameterIn; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -57,14 +58,14 @@ RouterFunction getSolveRoutes() { } private RouterFunction defineRouteForMetaSolver(MetaSolver metaSolver) { - String problemId = metaSolver.getProblemType().getId(); + var problemType = metaSolver.getProblemType(); return route().POST( - "/solve/" + problemId, + getSolveRouteForProblemType(problemType), accept(APPLICATION_JSON), req -> handleRouteForMetaSolver(metaSolver, req), ops -> ops - .operationId("/solve/" + problemId) - .tag(problemId) + .operationId(getSolveRouteForProblemType(problemType)) + .tag(problemType.getId()) .requestBody(requestBodyBuilder() .content(contentBuilder() .schema(schemaBuilder().implementation( @@ -110,14 +111,16 @@ RouterFunction getSolutionRoutes() { private RouterFunction defineSolutionRouteForMetaSolver( MetaSolver metaSolver) { - String problemId = metaSolver.getProblemType().getId(); + var problemType = metaSolver.getProblemType(); return route().GET( - "/solve/" + problemId, + // FIXME this is intentionally SOLVE instead of SOLUTION to avoid breaking things + // but maybe we should switch the name at some point + getSolveRouteForProblemType(problemType), accept(APPLICATION_JSON), req -> handleSolutionRouteForMetaSolver(metaSolver, req), ops -> ops - .operationId("/solution/" + problemId) - .tag(problemId) + .operationId(getSolutionRouteForProblemType(problemType)) + .tag(problemType.getId()) .parameter(parameterBuilder().in(ParameterIn.QUERY).name("id")) .response(responseBuilder() .responseCode(String.valueOf(HttpStatus.OK.value())) @@ -140,4 +143,12 @@ private Mono handleSolutionRouteForMetaSolver(MetaSolver>() { }); } + + private String getSolveRouteForProblemType(ProblemType type) { + return "/solve/" + type.getId(); + } + + private String getSolutionRouteForProblemType(ProblemType type) { + return "/solution/" + type.getId(); + } } diff --git a/src/main/java/edu/kit/provideq/toolbox/api/SolversRouter.java b/src/main/java/edu/kit/provideq/toolbox/api/SolversRouter.java index 07db7979..a9bc93a4 100644 --- a/src/main/java/edu/kit/provideq/toolbox/api/SolversRouter.java +++ b/src/main/java/edu/kit/provideq/toolbox/api/SolversRouter.java @@ -11,6 +11,7 @@ import edu.kit.provideq.toolbox.MetaSolverProvider; import edu.kit.provideq.toolbox.ProblemSolverInfo; import edu.kit.provideq.toolbox.meta.MetaSolver; +import edu.kit.provideq.toolbox.meta.ProblemType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -47,13 +48,13 @@ RouterFunction getSolversRoutes() { private RouterFunction defineSolversRouteForMetaSolver( MetaSolver metaSolver) { - String problemId = metaSolver.getProblemType().getId(); + var problemType = metaSolver.getProblemType(); return route().GET( - "/solvers/" + problemId, + getSolversRouteForProblemType(problemType), req -> handleSolversRouteForMetaSolver(metaSolver), ops -> ops - .operationId("/solvers/" + problemId) - .tag(problemId) + .operationId(getSolversRouteForProblemType(problemType)) + .tag(problemType.getId()) .response(responseBuilder() .responseCode(String.valueOf(HttpStatus.OK.value())) .content(contentBuilder() @@ -73,4 +74,8 @@ private Mono handleSolversRouteForMetaSolver(MetaSolver return ok().body(Mono.just(solvers), new ParameterizedTypeReference<>() { }); } + + private String getSolversRouteForProblemType(ProblemType type) { + return "/solvers/" + type.getId(); + } } diff --git a/src/main/java/edu/kit/provideq/toolbox/api/SubRoutineRouter.java b/src/main/java/edu/kit/provideq/toolbox/api/SubRoutineRouter.java index 8683c63e..8e56cc4b 100644 --- a/src/main/java/edu/kit/provideq/toolbox/api/SubRoutineRouter.java +++ b/src/main/java/edu/kit/provideq/toolbox/api/SubRoutineRouter.java @@ -12,6 +12,7 @@ import edu.kit.provideq.toolbox.MetaSolverProvider; import edu.kit.provideq.toolbox.meta.MetaSolver; import edu.kit.provideq.toolbox.meta.ProblemSolver; +import edu.kit.provideq.toolbox.meta.ProblemType; import edu.kit.provideq.toolbox.meta.SubRoutineDefinition; import io.swagger.v3.oas.annotations.enums.ParameterIn; import org.springframework.beans.factory.annotation.Autowired; @@ -52,14 +53,14 @@ RouterFunction getSubRoutineRoutes() { private RouterFunction defineSubRoutineRouteForMetaSolver( MetaSolver metaSolver) { - String problemId = metaSolver.getProblemType().getId(); + var problemType = metaSolver.getProblemType(); return route().GET( - "/sub-routines/" + problemId, + getSubRoutinesRouteForProblemType(problemType), req -> handleSubRoutineRouteForMetaSolver(metaSolver, req), ops -> ops - .operationId("/sub-routines/" + problemId) + .operationId(getSubRoutinesRouteForProblemType(problemType)) .parameter(parameterBuilder().in(ParameterIn.QUERY).name("id")) - .tag(problemId) + .tag(problemType.getId()) .response(responseBuilder() .responseCode(String.valueOf(HttpStatus.OK.value())) .content(contentBuilder() @@ -82,4 +83,8 @@ private Mono handleSubRoutineRouteForMetaSolver(MetaSolver() { }); } + + private String getSubRoutinesRouteForProblemType(ProblemType type) { + return "/sub-routines/" + type.getId(); + } } From 8edab8d9ea8f4613725a88b3497679ae7a383b0e Mon Sep 17 00:00:00 2001 From: Max Schweikart Date: Sat, 2 Sep 2023 22:03:38 +0200 Subject: [PATCH 17/22] refactor: fix typing of SolutionHandle#toStringSolution --- src/main/java/edu/kit/provideq/toolbox/Solution.java | 4 ++-- src/main/java/edu/kit/provideq/toolbox/SolutionHandle.java | 5 ++++- src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java | 4 +--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/Solution.java b/src/main/java/edu/kit/provideq/toolbox/Solution.java index c54b9fe7..02b11234 100644 --- a/src/main/java/edu/kit/provideq/toolbox/Solution.java +++ b/src/main/java/edu/kit/provideq/toolbox/Solution.java @@ -46,7 +46,7 @@ public void setStatus(SolutionStatus newStatus) { } @Override - public SolutionHandle toStringSolution() { + public Solution toStringSolution() { return toStringSolution(Object::toString); } @@ -58,7 +58,7 @@ public SolutionHandle toStringSolution() { * to a String. * @return the solution with the stringified solution data. */ - public SolutionHandle toStringSolution(@NotNull Function stringSelector) { + public Solution toStringSolution(@NotNull Function stringSelector) { Objects.requireNonNull(stringSelector, "Missing String selector!"); var stringSolution = new Solution(getId()); diff --git a/src/main/java/edu/kit/provideq/toolbox/SolutionHandle.java b/src/main/java/edu/kit/provideq/toolbox/SolutionHandle.java index daacb9a0..ece3663e 100644 --- a/src/main/java/edu/kit/provideq/toolbox/SolutionHandle.java +++ b/src/main/java/edu/kit/provideq/toolbox/SolutionHandle.java @@ -10,5 +10,8 @@ public interface SolutionHandle { void setStatus(SolutionStatus newStatus); - SolutionHandle toStringSolution(); + /** + * Converts this solution handle to a string-based {@link Solution}. + */ + Solution toStringSolution(); } diff --git a/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java b/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java index c7e60b52..8b86875e 100644 --- a/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java +++ b/src/main/java/edu/kit/provideq/toolbox/api/SolveRouter.java @@ -138,9 +138,7 @@ private Mono handleSolutionRouteForMetaSolver(MetaSolver new ResponseStatusException(HttpStatus.NOT_FOUND, "Could not find a solution for this problem with this solution id!")); - // yes, solution is of type `Solution`. - // No idea why `toStringSolution` returns `SolutionHandle` - return ok().body(Mono.just(solution), new ParameterizedTypeReference>() { + return ok().body(Mono.just(solution), new ParameterizedTypeReference<>() { }); } From 83123d2fd3d28a7bbce77138ed2d0be2ffa3d231 Mon Sep 17 00:00:00 2001 From: Max Schweikart Date: Sat, 2 Sep 2023 22:21:46 +0200 Subject: [PATCH 18/22] fix: re-enable CORS to allow frontend access --- .../toolbox/api/CorsConfiguration.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/main/java/edu/kit/provideq/toolbox/api/CorsConfiguration.java diff --git a/src/main/java/edu/kit/provideq/toolbox/api/CorsConfiguration.java b/src/main/java/edu/kit/provideq/toolbox/api/CorsConfiguration.java new file mode 100644 index 00000000..f788c55f --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/api/CorsConfiguration.java @@ -0,0 +1,19 @@ +package edu.kit.provideq.toolbox.api; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.reactive.config.CorsRegistry; +import org.springframework.web.reactive.config.EnableWebFlux; +import org.springframework.web.reactive.config.WebFluxConfigurer; + +/** + * Spring configuration to enable CORS between the API and the web frontend. + */ +@Configuration +@EnableWebFlux +public class CorsConfiguration implements WebFluxConfigurer { + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOrigins("*"); + } +} From b0c11b7bfa59b035f671a4215809647a98bed86f Mon Sep 17 00:00:00 2001 From: Max Schweikart Date: Tue, 1 Aug 2023 14:14:15 +0200 Subject: [PATCH 19/22] refactor: split up feature model anomaly solver by anomalies --- .../anomaly/FeatureModelAnomaly.java | 27 ------- .../anomaly/FeatureModelAnomalyProblem.java | 4 - .../MetaSolverFeatureModelAnomaly.java | 35 --------- .../anomaly/dead/DeadFeatureMetaSolver.java | 24 ++++++ .../DeadFeatureSolver.java} | 53 +++---------- .../anomaly/solvers/FeatureModelSolver.java | 8 -- .../voidmodel/VoidFeatureMetaSolver.java | 26 +++++++ .../anomaly/voidmodel/VoidFeatureSolver.java | 75 +++++++++++++++++++ .../provideq/toolbox/meta/ProblemType.java | 3 +- .../api/FeatureModelAnomalySolverTest.java | 43 +++++++---- 10 files changed, 163 insertions(+), 135 deletions(-) delete mode 100644 src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/FeatureModelAnomaly.java delete mode 100644 src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/FeatureModelAnomalyProblem.java delete mode 100644 src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/MetaSolverFeatureModelAnomaly.java create mode 100644 src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/dead/DeadFeatureMetaSolver.java rename src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/{solvers/FeatureModelAnomalySolver.java => dead/DeadFeatureSolver.java} (60%) delete mode 100644 src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/solvers/FeatureModelSolver.java create mode 100644 src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/voidmodel/VoidFeatureMetaSolver.java create mode 100644 src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/voidmodel/VoidFeatureSolver.java diff --git a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/FeatureModelAnomaly.java b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/FeatureModelAnomaly.java deleted file mode 100644 index 5af274c7..00000000 --- a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/FeatureModelAnomaly.java +++ /dev/null @@ -1,27 +0,0 @@ -package edu.kit.provideq.toolbox.featuremodel.anomaly; - -public enum FeatureModelAnomaly { - /** - * Occurs when no configuration of the feature model is ever valid. - */ - VOID("Void Feature Model"), - /** - * Occurs when a feature is never true in any configuration. - */ - DEAD("Dead Features"), - /** - * Occurs when a feature is flagged as optional, but in reality is mandatory and exists in all - * configurations. - */ - FALSE_OPTIONAL("False-optional Features"), - /** - * Constraints that don't have an impact on the valid configurations. - */ - REDUNDANT_CONSTRAINTS("Redundant Constraints"); - - public final String name; - - FeatureModelAnomaly(String name) { - this.name = name; - } -} diff --git a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/FeatureModelAnomalyProblem.java b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/FeatureModelAnomalyProblem.java deleted file mode 100644 index 6ab374ee..00000000 --- a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/FeatureModelAnomalyProblem.java +++ /dev/null @@ -1,4 +0,0 @@ -package edu.kit.provideq.toolbox.featuremodel.anomaly; - -public record FeatureModelAnomalyProblem(String featureModel, FeatureModelAnomaly anomaly) { -} diff --git a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/MetaSolverFeatureModelAnomaly.java b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/MetaSolverFeatureModelAnomaly.java deleted file mode 100644 index be75dc1c..00000000 --- a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/MetaSolverFeatureModelAnomaly.java +++ /dev/null @@ -1,35 +0,0 @@ -package edu.kit.provideq.toolbox.featuremodel.anomaly; - -import edu.kit.provideq.toolbox.featuremodel.anomaly.solvers.FeatureModelAnomalySolver; -import edu.kit.provideq.toolbox.meta.MetaSolver; -import edu.kit.provideq.toolbox.meta.Problem; -import edu.kit.provideq.toolbox.meta.ProblemType; -import edu.kit.provideq.toolbox.meta.setting.MetaSolverSetting; -import java.util.ArrayList; -import java.util.List; -import java.util.Random; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -/** - * Simple {@link MetaSolver} for FeatureModel problems. - */ -@Component -public class MetaSolverFeatureModelAnomaly extends MetaSolver< - FeatureModelAnomalyProblem, - String, - FeatureModelAnomalySolver> { - - @Autowired - public MetaSolverFeatureModelAnomaly(FeatureModelAnomalySolver anomalySolver) { - super(ProblemType.FEATURE_MODEL_ANOMALY, anomalySolver); - } - - @Override - public FeatureModelAnomalySolver findSolver( - Problem problem, - List metaSolverSettings) { - // todo add decision - return (new ArrayList<>(this.solvers)).get((new Random()).nextInt(this.solvers.size())); - } -} diff --git a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/dead/DeadFeatureMetaSolver.java b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/dead/DeadFeatureMetaSolver.java new file mode 100644 index 00000000..52f508e6 --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/dead/DeadFeatureMetaSolver.java @@ -0,0 +1,24 @@ +package edu.kit.provideq.toolbox.featuremodel.anomaly.dead; + +import edu.kit.provideq.toolbox.meta.MetaSolver; +import edu.kit.provideq.toolbox.meta.Problem; +import edu.kit.provideq.toolbox.meta.ProblemType; +import edu.kit.provideq.toolbox.meta.setting.MetaSolverSetting; +import java.util.List; +import org.springframework.stereotype.Component; + +@Component +public class DeadFeatureMetaSolver extends MetaSolver { + private final DeadFeatureSolver solver = new DeadFeatureSolver(); + + public DeadFeatureMetaSolver(DeadFeatureSolver solver) { + super(ProblemType.FEATURE_MODEL_ANOMALY_DEAD, solver); + } + + @Override + public DeadFeatureSolver findSolver(Problem problem, + List metaSolverSettings) { + // we only have one solver at this point + return solver; + } +} diff --git a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/solvers/FeatureModelAnomalySolver.java b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/dead/DeadFeatureSolver.java similarity index 60% rename from src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/solvers/FeatureModelAnomalySolver.java rename to src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/dead/DeadFeatureSolver.java index d06c47b3..62f8d1cd 100644 --- a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/solvers/FeatureModelAnomalySolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/dead/DeadFeatureSolver.java @@ -1,15 +1,15 @@ -package edu.kit.provideq.toolbox.featuremodel.anomaly.solvers; +package edu.kit.provideq.toolbox.featuremodel.anomaly.dead; import edu.kit.provideq.toolbox.Solution; import edu.kit.provideq.toolbox.SolutionStatus; import edu.kit.provideq.toolbox.SubRoutinePool; import edu.kit.provideq.toolbox.convert.UvlToDimacsCnf; import edu.kit.provideq.toolbox.exception.ConversionException; -import edu.kit.provideq.toolbox.featuremodel.anomaly.FeatureModelAnomalyProblem; import edu.kit.provideq.toolbox.format.cnf.dimacs.DimacsCnf; import edu.kit.provideq.toolbox.format.cnf.dimacs.DimacsCnfSolution; import edu.kit.provideq.toolbox.format.cnf.dimacs.Variable; import edu.kit.provideq.toolbox.meta.Problem; +import edu.kit.provideq.toolbox.meta.ProblemSolver; import edu.kit.provideq.toolbox.meta.ProblemType; import edu.kit.provideq.toolbox.meta.SubRoutineDefinition; import java.util.ArrayList; @@ -18,10 +18,10 @@ import org.springframework.stereotype.Component; @Component -public class FeatureModelAnomalySolver extends FeatureModelSolver { +public class DeadFeatureSolver implements ProblemSolver { @Override public String getName() { - return "Feature Model Anomaly"; + return "SAT-based Dead Feature Solver"; } @Override @@ -32,36 +32,24 @@ public List getSubRoutines() { } @Override - public boolean canSolve(Problem problem) { - //TODO: assess problemData - return problem.type() == ProblemType.FEATURE_MODEL_ANOMALY; + public boolean canSolve(Problem problem) { + return problem.type() == ProblemType.FEATURE_MODEL_ANOMALY_DEAD; } @Override - public void solve(Problem problem, Solution solution, + public void solve(Problem problem, Solution solution, SubRoutinePool subRoutinePool) { // Convert uvl to cnf String cnf; try { - cnf = UvlToDimacsCnf.convert(problem.problemData().featureModel()); + cnf = UvlToDimacsCnf.convert(problem.problemData()); } catch (ConversionException e) { solution.setDebugData("Conversion error: " + e.getMessage()); return; } var satSolve = subRoutinePool.getSubRoutine(ProblemType.SAT); - switch (problem.problemData().anomaly()) { - case VOID -> checkVoidFeatureModel(solution, cnf, satSolve); - case DEAD -> checkDeadFeatures(solution, cnf, satSolve); - case FALSE_OPTIONAL, REDUNDANT_CONSTRAINTS -> { - solution.setDebugData("Not implemented yet!"); - solution.abort(); - } - default -> { - solution.setDebugData("Unknown anomaly type " + problem.problemData().anomaly() + "!"); - solution.abort(); - } - } + checkDeadFeatures(solution, cnf, satSolve); } private static void checkDeadFeatures(Solution solution, String cnf, @@ -118,27 +106,4 @@ private static void checkDeadFeatures(Solution solution, String cnf, solution.setSolutionData(builder.toString()); solution.complete(); } - - private static void checkVoidFeatureModel(Solution solution, - String cnf, - Function> satSolve) { - // Check if the feature model is not a void feature model - var voidSolution = satSolve.apply(cnf); - - solution.setDebugData("Dimacs CNF of Feature Model:\n" + cnf); - if (voidSolution.getStatus() == SolutionStatus.SOLVED) { - // If there is a valid configuration, the feature model is not a void feature model - var dimacsCnfSolution = voidSolution.getSolutionData(); - - solution.setSolutionData(voidSolution.getSolutionData().isVoid() - ? "The feature model is a void feature model. The configuration is never valid." - : "The feature model has valid configurations, for example: \n" - + dimacsCnfSolution.toHumanReadableString()); - solution.complete(); - } else { - solution.setDebugData(voidSolution.getDebugData()); - solution.abort(); - } - } } diff --git a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/solvers/FeatureModelSolver.java b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/solvers/FeatureModelSolver.java deleted file mode 100644 index 08ac67d6..00000000 --- a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/solvers/FeatureModelSolver.java +++ /dev/null @@ -1,8 +0,0 @@ -package edu.kit.provideq.toolbox.featuremodel.anomaly.solvers; - -import edu.kit.provideq.toolbox.featuremodel.anomaly.FeatureModelAnomalyProblem; -import edu.kit.provideq.toolbox.meta.ProblemSolver; - -public abstract class FeatureModelSolver - implements ProblemSolver { -} diff --git a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/voidmodel/VoidFeatureMetaSolver.java b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/voidmodel/VoidFeatureMetaSolver.java new file mode 100644 index 00000000..29f75be3 --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/voidmodel/VoidFeatureMetaSolver.java @@ -0,0 +1,26 @@ +package edu.kit.provideq.toolbox.featuremodel.anomaly.voidmodel; + +import edu.kit.provideq.toolbox.meta.MetaSolver; +import edu.kit.provideq.toolbox.meta.Problem; +import edu.kit.provideq.toolbox.meta.ProblemSolver; +import edu.kit.provideq.toolbox.meta.ProblemType; +import edu.kit.provideq.toolbox.meta.setting.MetaSolverSetting; +import java.util.List; +import org.springframework.stereotype.Component; + +@Component +public class VoidFeatureMetaSolver + extends MetaSolver> { + private final VoidFeatureSolver solver; + + public VoidFeatureMetaSolver(VoidFeatureSolver solver) { + super(ProblemType.FEATURE_MODEL_ANOMALY_VOID, solver); + this.solver = solver; + } + + @Override + public ProblemSolver findSolver(Problem problem, + List metaSolverSettings) { + return this.solver; + } +} diff --git a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/voidmodel/VoidFeatureSolver.java b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/voidmodel/VoidFeatureSolver.java new file mode 100644 index 00000000..efebb684 --- /dev/null +++ b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/voidmodel/VoidFeatureSolver.java @@ -0,0 +1,75 @@ +package edu.kit.provideq.toolbox.featuremodel.anomaly.voidmodel; + +import edu.kit.provideq.toolbox.Solution; +import edu.kit.provideq.toolbox.SolutionStatus; +import edu.kit.provideq.toolbox.SubRoutinePool; +import edu.kit.provideq.toolbox.convert.UvlToDimacsCnf; +import edu.kit.provideq.toolbox.exception.ConversionException; +import edu.kit.provideq.toolbox.format.cnf.dimacs.DimacsCnfSolution; +import edu.kit.provideq.toolbox.meta.Problem; +import edu.kit.provideq.toolbox.meta.ProblemSolver; +import edu.kit.provideq.toolbox.meta.ProblemType; +import edu.kit.provideq.toolbox.meta.SubRoutineDefinition; +import java.util.List; +import java.util.function.Function; +import org.springframework.stereotype.Component; + +@Component +public class VoidFeatureSolver + implements ProblemSolver { + @Override + public String getName() { + return "SAT-based Void Feature Model Solver"; + } + + @Override + public List getSubRoutines() { + return List.of( + new SubRoutineDefinition(ProblemType.SAT, "sat", + "Used to find valid configurations in the Feature Model")); + } + + @Override + public boolean canSolve(Problem problem) { + return problem.type() == ProblemType.FEATURE_MODEL_ANOMALY_VOID; + } + + @Override + public void solve(Problem problem, Solution solution, + SubRoutinePool subRoutinePool) { + // Convert uvl to cnf + String cnf; + try { + cnf = UvlToDimacsCnf.convert(problem.problemData()); + } catch (ConversionException e) { + solution.setDebugData("Conversion error: " + e.getMessage()); + return; + } + + var satSolve = subRoutinePool.getSubRoutine(ProblemType.SAT); + checkVoidFeatureModel(solution, cnf, satSolve); + } + + private static void checkVoidFeatureModel(Solution solution, + String cnf, + Function> satSolve) { + // Check if the feature model is not a void feature model + var voidSolution = satSolve.apply(cnf); + + solution.setDebugData("Dimacs CNF of Feature Model:\n" + cnf); + if (voidSolution.getStatus() == SolutionStatus.SOLVED) { + // If there is a valid configuration, the feature model is not a void feature model + var dimacsCnfSolution = voidSolution.getSolutionData(); + + solution.setSolutionData(voidSolution.getSolutionData().isVoid() + ? "The feature model is a void feature model. The configuration is never valid." + : "The feature model has valid configurations, for example: \n" + + dimacsCnfSolution.toHumanReadableString()); + solution.complete(); + } else { + solution.setDebugData(voidSolution.getDebugData()); + solution.abort(); + } + } +} diff --git a/src/main/java/edu/kit/provideq/toolbox/meta/ProblemType.java b/src/main/java/edu/kit/provideq/toolbox/meta/ProblemType.java index e904bed3..235715a7 100644 --- a/src/main/java/edu/kit/provideq/toolbox/meta/ProblemType.java +++ b/src/main/java/edu/kit/provideq/toolbox/meta/ProblemType.java @@ -29,7 +29,8 @@ public enum ProblemType { * More information */ FEATURE_MODEL_ANOMALY("feature-model-anomaly", SolveFeatureModelRequest.class), - ; + FEATURE_MODEL_ANOMALY_DEAD("feature-model-anomaly-dead", SolveFeatureModelRequest.class), + FEATURE_MODEL_ANOMALY_VOID("feature-model-anomaly-void", SolveFeatureModelRequest.class); private final String id; private final Class> requestType; diff --git a/src/test/java/edu/kit/provideq/toolbox/api/FeatureModelAnomalySolverTest.java b/src/test/java/edu/kit/provideq/toolbox/api/FeatureModelAnomalySolverTest.java index 914e1650..18714345 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/FeatureModelAnomalySolverTest.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/FeatureModelAnomalySolverTest.java @@ -3,13 +3,21 @@ import static edu.kit.provideq.toolbox.SolutionStatus.SOLVED; import static org.hamcrest.Matchers.is; +import edu.kit.provideq.toolbox.GamsProcessRunner; import edu.kit.provideq.toolbox.MetaSolverProvider; +import edu.kit.provideq.toolbox.ResourceProvider; import edu.kit.provideq.toolbox.Solution; import edu.kit.provideq.toolbox.SolutionStatus; import edu.kit.provideq.toolbox.SubRoutinePool; import edu.kit.provideq.toolbox.featuremodel.SolveFeatureModelRequest; -import edu.kit.provideq.toolbox.featuremodel.anomaly.MetaSolverFeatureModelAnomaly; -import edu.kit.provideq.toolbox.featuremodel.anomaly.solvers.FeatureModelAnomalySolver; +import edu.kit.provideq.toolbox.featuremodel.anomaly.dead.DeadFeatureMetaSolver; +import edu.kit.provideq.toolbox.featuremodel.anomaly.dead.DeadFeatureSolver; +import edu.kit.provideq.toolbox.featuremodel.anomaly.voidmodel.VoidFeatureMetaSolver; +import edu.kit.provideq.toolbox.featuremodel.anomaly.voidmodel.VoidFeatureSolver; +import edu.kit.provideq.toolbox.meta.ProblemSolver; +import edu.kit.provideq.toolbox.meta.ProblemType; +import edu.kit.provideq.toolbox.sat.MetaSolverSat; +import edu.kit.provideq.toolbox.sat.solvers.GamsSatSolver; import java.util.stream.Stream; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -26,32 +34,35 @@ @Import(value = { SolveRouter.class, MetaSolverProvider.class, - MetaSolverFeatureModelAnomaly.class, - FeatureModelAnomalySolver.class, - SubRoutinePool.class + DeadFeatureMetaSolver.class, + DeadFeatureSolver.class, + VoidFeatureMetaSolver.class, + VoidFeatureSolver.class, + SubRoutinePool.class, + MetaSolverSat.class, + GamsSatSolver.class, + GamsProcessRunner.class, + ResourceProvider.class, }) class FeatureModelAnomalySolverTest { @Autowired private WebTestClient client; static Stream provideAnomalySolverIds() { - String solverId = FeatureModelAnomalySolver.class.getName(); return Stream.of( - Arguments.of(solverId, "void", SOLVED), - Arguments.of(solverId, "dead", SOLVED), - - // not implemented yet, change to SOLVED when they have been implemented! - Arguments.of(solverId, "false-optional", SolutionStatus.INVALID), - Arguments.of(solverId, "redundant-constraints", SolutionStatus.INVALID) + Arguments.of(VoidFeatureSolver.class, ProblemType.FEATURE_MODEL_ANOMALY_VOID, SOLVED), + Arguments.of(DeadFeatureSolver.class, ProblemType.FEATURE_MODEL_ANOMALY_DEAD, SOLVED) ); } @ParameterizedTest @MethodSource("provideAnomalySolverIds") - void testFeatureModelAnomalySolver(String solverId, String anomalyType, - SolutionStatus expectedStatus) { + void testFeatureModelAnomalySolver( + Class> solver, + ProblemType anomalyType, + SolutionStatus expectedStatus) { var req = new SolveFeatureModelRequest(); - req.requestedSolverId = solverId; + req.requestedSolverId = solver.getName(); req.requestContent = """ namespace Sandwich @@ -85,7 +96,7 @@ void testFeatureModelAnomalySolver(String solverId, String anomalyType, """; var response = client.post() - .uri("/solve/feature-model-anomaly/" + anomalyType) // FIXME type of anomaly? + .uri("/solve/" + anomalyType.getId()) .contentType(MediaType.APPLICATION_JSON) .bodyValue(req) .exchange(); From a8f50d74422d037f3e77a02a4ec7491d41466c01 Mon Sep 17 00:00:00 2001 From: Max Schweikart Date: Thu, 7 Sep 2023 12:30:20 +0200 Subject: [PATCH 20/22] refactor: remove unused FEATURE_MODEL_ANOMALY problem type --- .../kit/provideq/toolbox/meta/ProblemType.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/meta/ProblemType.java b/src/main/java/edu/kit/provideq/toolbox/meta/ProblemType.java index 235715a7..00ca4498 100644 --- a/src/main/java/edu/kit/provideq/toolbox/meta/ProblemType.java +++ b/src/main/java/edu/kit/provideq/toolbox/meta/ProblemType.java @@ -24,12 +24,19 @@ public enum ProblemType { /** * A searching problem: - * For a given feature model, check for Void Feature Model, Dead Features, - * False-Optional Features, Redundant Constraints. - * More information + * For a given feature model, check if the model contains dead features. + * + * @see + * "Explaining Anomalies in Feature Models", Kowal et al., 2026 */ - FEATURE_MODEL_ANOMALY("feature-model-anomaly", SolveFeatureModelRequest.class), FEATURE_MODEL_ANOMALY_DEAD("feature-model-anomaly-dead", SolveFeatureModelRequest.class), + /** + * A searching problem: + * For a given feature model, check if the model is void. + * + * @see + * "Explaining Anomalies in Feature Models", Kowal et al., 2026 + */ FEATURE_MODEL_ANOMALY_VOID("feature-model-anomaly-void", SolveFeatureModelRequest.class); private final String id; From 936a21caba31f0d10ef6d2a64eb20ebb4e3432d1 Mon Sep 17 00:00:00 2001 From: Max Schweikart Date: Thu, 7 Sep 2023 12:51:54 +0200 Subject: [PATCH 21/22] refactor: rename feature model anomaly solvers for more clarity --- .../anomaly/dead/DeadFeatureMetaSolver.java | 17 ++++++++++------- ...lver.java => SatBasedDeadFeatureSolver.java} | 6 +++++- ...lver.java => SatBasedVoidFeatureSolver.java} | 6 +++++- .../voidmodel/VoidFeatureMetaSolver.java | 11 ++++++----- .../api/FeatureModelAnomalySolverTest.java | 14 ++++++++------ 5 files changed, 34 insertions(+), 20 deletions(-) rename src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/dead/{DeadFeatureSolver.java => SatBasedDeadFeatureSolver.java} (93%) rename src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/voidmodel/{VoidFeatureSolver.java => SatBasedVoidFeatureSolver.java} (92%) diff --git a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/dead/DeadFeatureMetaSolver.java b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/dead/DeadFeatureMetaSolver.java index 52f508e6..e100718a 100644 --- a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/dead/DeadFeatureMetaSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/dead/DeadFeatureMetaSolver.java @@ -2,23 +2,26 @@ import edu.kit.provideq.toolbox.meta.MetaSolver; import edu.kit.provideq.toolbox.meta.Problem; +import edu.kit.provideq.toolbox.meta.ProblemSolver; import edu.kit.provideq.toolbox.meta.ProblemType; import edu.kit.provideq.toolbox.meta.setting.MetaSolverSetting; import java.util.List; import org.springframework.stereotype.Component; +/** + * This is the meta solver for the {@link ProblemType#FEATURE_MODEL_ANOMALY_DEAD} problem. + */ @Component -public class DeadFeatureMetaSolver extends MetaSolver { - private final DeadFeatureSolver solver = new DeadFeatureSolver(); - - public DeadFeatureMetaSolver(DeadFeatureSolver solver) { +public class DeadFeatureMetaSolver + extends MetaSolver> { + public DeadFeatureMetaSolver(SatBasedDeadFeatureSolver solver) { super(ProblemType.FEATURE_MODEL_ANOMALY_DEAD, solver); } @Override - public DeadFeatureSolver findSolver(Problem problem, - List metaSolverSettings) { + public ProblemSolver findSolver(Problem problem, + List metaSolverSettings) { // we only have one solver at this point - return solver; + return getAllSolvers().stream().findAny().orElseThrow(); } } diff --git a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/dead/DeadFeatureSolver.java b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/dead/SatBasedDeadFeatureSolver.java similarity index 93% rename from src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/dead/DeadFeatureSolver.java rename to src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/dead/SatBasedDeadFeatureSolver.java index 62f8d1cd..e9d2dc3c 100644 --- a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/dead/DeadFeatureSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/dead/SatBasedDeadFeatureSolver.java @@ -17,8 +17,12 @@ import java.util.function.Function; import org.springframework.stereotype.Component; +/** + * This problem solver solves the {@link ProblemType#FEATURE_MODEL_ANOMALY_DEAD} problem by building + * {@link ProblemType#SAT} formulae that are solved by a corresponding solver. + */ @Component -public class DeadFeatureSolver implements ProblemSolver { +public class SatBasedDeadFeatureSolver implements ProblemSolver { @Override public String getName() { return "SAT-based Dead Feature Solver"; diff --git a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/voidmodel/VoidFeatureSolver.java b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/voidmodel/SatBasedVoidFeatureSolver.java similarity index 92% rename from src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/voidmodel/VoidFeatureSolver.java rename to src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/voidmodel/SatBasedVoidFeatureSolver.java index efebb684..9914cb4a 100644 --- a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/voidmodel/VoidFeatureSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/voidmodel/SatBasedVoidFeatureSolver.java @@ -14,8 +14,12 @@ import java.util.function.Function; import org.springframework.stereotype.Component; +/** + * This problem solver solves the {@link ProblemType#FEATURE_MODEL_ANOMALY_VOID} problem by building + * {@link ProblemType#SAT} formula that is solved by a corresponding solver. + */ @Component -public class VoidFeatureSolver +public class SatBasedVoidFeatureSolver implements ProblemSolver { @Override public String getName() { diff --git a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/voidmodel/VoidFeatureMetaSolver.java b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/voidmodel/VoidFeatureMetaSolver.java index 29f75be3..bdac824c 100644 --- a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/voidmodel/VoidFeatureMetaSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/voidmodel/VoidFeatureMetaSolver.java @@ -8,19 +8,20 @@ import java.util.List; import org.springframework.stereotype.Component; +/** + * This is the meta solver for the {@link ProblemType#FEATURE_MODEL_ANOMALY_VOID} problem. + */ @Component public class VoidFeatureMetaSolver extends MetaSolver> { - private final VoidFeatureSolver solver; - - public VoidFeatureMetaSolver(VoidFeatureSolver solver) { + public VoidFeatureMetaSolver(SatBasedVoidFeatureSolver solver) { super(ProblemType.FEATURE_MODEL_ANOMALY_VOID, solver); - this.solver = solver; } @Override public ProblemSolver findSolver(Problem problem, List metaSolverSettings) { - return this.solver; + // we only have one solver at this point + return getAllSolvers().stream().findAny().orElseThrow(); } } diff --git a/src/test/java/edu/kit/provideq/toolbox/api/FeatureModelAnomalySolverTest.java b/src/test/java/edu/kit/provideq/toolbox/api/FeatureModelAnomalySolverTest.java index 18714345..47b51689 100644 --- a/src/test/java/edu/kit/provideq/toolbox/api/FeatureModelAnomalySolverTest.java +++ b/src/test/java/edu/kit/provideq/toolbox/api/FeatureModelAnomalySolverTest.java @@ -11,9 +11,9 @@ import edu.kit.provideq.toolbox.SubRoutinePool; import edu.kit.provideq.toolbox.featuremodel.SolveFeatureModelRequest; import edu.kit.provideq.toolbox.featuremodel.anomaly.dead.DeadFeatureMetaSolver; -import edu.kit.provideq.toolbox.featuremodel.anomaly.dead.DeadFeatureSolver; +import edu.kit.provideq.toolbox.featuremodel.anomaly.dead.SatBasedDeadFeatureSolver; +import edu.kit.provideq.toolbox.featuremodel.anomaly.voidmodel.SatBasedVoidFeatureSolver; import edu.kit.provideq.toolbox.featuremodel.anomaly.voidmodel.VoidFeatureMetaSolver; -import edu.kit.provideq.toolbox.featuremodel.anomaly.voidmodel.VoidFeatureSolver; import edu.kit.provideq.toolbox.meta.ProblemSolver; import edu.kit.provideq.toolbox.meta.ProblemType; import edu.kit.provideq.toolbox.sat.MetaSolverSat; @@ -35,9 +35,9 @@ SolveRouter.class, MetaSolverProvider.class, DeadFeatureMetaSolver.class, - DeadFeatureSolver.class, + SatBasedDeadFeatureSolver.class, VoidFeatureMetaSolver.class, - VoidFeatureSolver.class, + SatBasedVoidFeatureSolver.class, SubRoutinePool.class, MetaSolverSat.class, GamsSatSolver.class, @@ -50,8 +50,10 @@ class FeatureModelAnomalySolverTest { static Stream provideAnomalySolverIds() { return Stream.of( - Arguments.of(VoidFeatureSolver.class, ProblemType.FEATURE_MODEL_ANOMALY_VOID, SOLVED), - Arguments.of(DeadFeatureSolver.class, ProblemType.FEATURE_MODEL_ANOMALY_DEAD, SOLVED) + Arguments.of(SatBasedVoidFeatureSolver.class, + ProblemType.FEATURE_MODEL_ANOMALY_VOID, SOLVED), + Arguments.of(SatBasedDeadFeatureSolver.class, + ProblemType.FEATURE_MODEL_ANOMALY_DEAD, SOLVED) ); } From 14e77fc31bce607ad6d3623a37ddf233832116f2 Mon Sep 17 00:00:00 2001 From: Max Schweikart Date: Fri, 8 Sep 2023 16:45:55 +0200 Subject: [PATCH 22/22] refactor: remove redundant problem type spec in SubRoutineDefinition --- .../dead/SatBasedDeadFeatureSolver.java | 2 +- .../voidmodel/SatBasedVoidFeatureSolver.java | 2 +- .../toolbox/meta/SubRoutineDefinition.java | 24 ++++++++++++++----- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/dead/SatBasedDeadFeatureSolver.java b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/dead/SatBasedDeadFeatureSolver.java index e9d2dc3c..b9c93358 100644 --- a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/dead/SatBasedDeadFeatureSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/dead/SatBasedDeadFeatureSolver.java @@ -31,7 +31,7 @@ public String getName() { @Override public List getSubRoutines() { return List.of( - new SubRoutineDefinition(ProblemType.SAT, "sat", + new SubRoutineDefinition(ProblemType.SAT, "Used to find valid configurations in the Feature Model")); } diff --git a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/voidmodel/SatBasedVoidFeatureSolver.java b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/voidmodel/SatBasedVoidFeatureSolver.java index 9914cb4a..0b039bec 100644 --- a/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/voidmodel/SatBasedVoidFeatureSolver.java +++ b/src/main/java/edu/kit/provideq/toolbox/featuremodel/anomaly/voidmodel/SatBasedVoidFeatureSolver.java @@ -29,7 +29,7 @@ public String getName() { @Override public List getSubRoutines() { return List.of( - new SubRoutineDefinition(ProblemType.SAT, "sat", + new SubRoutineDefinition(ProblemType.SAT, "Used to find valid configurations in the Feature Model")); } diff --git a/src/main/java/edu/kit/provideq/toolbox/meta/SubRoutineDefinition.java b/src/main/java/edu/kit/provideq/toolbox/meta/SubRoutineDefinition.java index 997a7252..7597252f 100644 --- a/src/main/java/edu/kit/provideq/toolbox/meta/SubRoutineDefinition.java +++ b/src/main/java/edu/kit/provideq/toolbox/meta/SubRoutineDefinition.java @@ -1,12 +1,24 @@ package edu.kit.provideq.toolbox.meta; /** - * Definition of a sub routine that holds basic information. + * A sub-routine definition describes which problem type needs to be solved by a sub-routine and why + * it needs to be solved. * - * @param type type of the problem that is used in the sub routine - * @param url url of the problem to call the sub routine - * @param description description of the sub routine call to provide information where and why it is - * needed + * @param problemTypeId {@link ProblemType#getId() id} of the problem type that needs to be solved + * by this sub-routine. + * @param description description of the sub-routine call to provide information where and why it is + * needed. + * @see #SubRoutineDefinition(ProblemType, String) */ -public record SubRoutineDefinition(ProblemType type, String url, String description) { +public record SubRoutineDefinition(String problemTypeId, String description) { + /** + * Creates a sub-routine definition for a given problem type with a given description. + * + * @param type problem type that needs to be solved by this sub-routine. + * @param description description of the sub-routine call to provide information where and why it + * is needed. + */ + public SubRoutineDefinition(ProblemType type, String description) { + this(type.getId(), description); + } }