Compare commits
	
		
			No commits in common. "1e3e01be23f17a0b0f61892cc2349569fe3f0ecb" and "a613a92adb71edbf24cc0d99a79c3344929c669e" have entirely different histories.
		
	
	
		
			1e3e01be23
			...
			a613a92adb
		
	
		
					 10 changed files with 11 additions and 233 deletions
				
			
		|  | @ -4,9 +4,4 @@ COPY target/maze-server-*.jar /app/ | ||||||
| RUN rm /app/*-sources.jar | RUN rm /app/*-sources.jar | ||||||
| RUN mv /app/*.jar /app/app.jar | RUN mv /app/*.jar /app/app.jar | ||||||
| 
 | 
 | ||||||
| CMD java \ | CMD java -Dfritteli.maze.server.host=0.0.0.0 -Dfritteli.maze.server.port=80 -jar /app/app.jar | ||||||
|     -Dfritteli.maze.server.host=0.0.0.0 \ |  | ||||||
|     -Dfritteli.maze.server.port=80 \ |  | ||||||
|     -Dfritteli.maze.maxheight=256 \ |  | ||||||
|     -Dfritteli.maze.maxwidth=256 \ |  | ||||||
|     -jar /app/app.jar |  | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								pom.xml
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								pom.xml
									
										
									
									
									
								
							|  | @ -56,7 +56,7 @@ | ||||||
|     </distributionManagement> |     </distributionManagement> | ||||||
| 
 | 
 | ||||||
|     <properties> |     <properties> | ||||||
|         <maze-generator.version>0.3.0</maze-generator.version> |         <maze-generator.version>0.2.1</maze-generator.version> | ||||||
|         <maven-site-plugin.version>4.0.0-M8</maven-site-plugin.version> |         <maven-site-plugin.version>4.0.0-M8</maven-site-plugin.version> | ||||||
|         <undertow.version>2.3.18.Final</undertow.version> |         <undertow.version>2.3.18.Final</undertow.version> | ||||||
|     </properties> |     </properties> | ||||||
|  |  | ||||||
|  | @ -1,45 +0,0 @@ | ||||||
| package ch.fritteli.maze.server; |  | ||||||
| 
 |  | ||||||
| import ch.fritteli.maze.generator.algorithm.MazeGeneratorAlgorithm; |  | ||||||
| import ch.fritteli.maze.generator.algorithm.RandomDepthFirst; |  | ||||||
| import ch.fritteli.maze.generator.algorithm.wilson.Wilson; |  | ||||||
| import ch.fritteli.maze.generator.model.Maze; |  | ||||||
| import io.vavr.collection.List; |  | ||||||
| import io.vavr.collection.Stream; |  | ||||||
| import io.vavr.control.Option; |  | ||||||
| import lombok.Getter; |  | ||||||
| import org.jetbrains.annotations.NotNull; |  | ||||||
| import org.jetbrains.annotations.Nullable; |  | ||||||
| 
 |  | ||||||
| import java.util.function.Function; |  | ||||||
| 
 |  | ||||||
| public enum Algorithm { |  | ||||||
|     RANDOM_DEPTH_FIRST(RandomDepthFirst::new, "random", "random-depth-first"), |  | ||||||
|     WILSON(Wilson::new, "wilson"); |  | ||||||
| 
 |  | ||||||
|     @NotNull |  | ||||||
|     private final Function<Maze, MazeGeneratorAlgorithm> creator; |  | ||||||
| 
 |  | ||||||
|     @Getter |  | ||||||
|     @NotNull |  | ||||||
|     private final List<String> names; |  | ||||||
| 
 |  | ||||||
|     Algorithm(@NotNull final Function<Maze, MazeGeneratorAlgorithm> creator, |  | ||||||
|               @NotNull final String... names) { |  | ||||||
|         this.creator = creator; |  | ||||||
|         this.names = List.of(names); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @NotNull |  | ||||||
|     public static Option<Algorithm> ofString(@Nullable final String name) { |  | ||||||
|         return Option.of(name) |  | ||||||
|                 .map(String::toLowerCase) |  | ||||||
|                 .flatMap(nameLC -> Stream.of(values()) |  | ||||||
|                         .find(algorithm -> algorithm.getNames().contains(nameLC))); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @NotNull |  | ||||||
|     public MazeGeneratorAlgorithm createAlgorithm(@NotNull final Maze maze) { |  | ||||||
|         return this.creator.apply(maze); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -3,8 +3,6 @@ package ch.fritteli.maze.server; | ||||||
| import ch.fritteli.maze.server.handler.CreateHandler; | import ch.fritteli.maze.server.handler.CreateHandler; | ||||||
| import ch.fritteli.maze.server.handler.RenderV1Handler; | import ch.fritteli.maze.server.handler.RenderV1Handler; | ||||||
| import ch.fritteli.maze.server.handler.RenderV2Handler; | import ch.fritteli.maze.server.handler.RenderV2Handler; | ||||||
| import ch.fritteli.maze.server.handler.RenderV3Handler; |  | ||||||
| import ch.fritteli.maze.server.handler.RenderVxHandler; |  | ||||||
| import io.undertow.Undertow; | import io.undertow.Undertow; | ||||||
| import io.undertow.server.RoutingHandler; | import io.undertow.server.RoutingHandler; | ||||||
| import io.vavr.control.Try; | import io.vavr.control.Try; | ||||||
|  | @ -26,9 +24,7 @@ public class MazeServer { | ||||||
|         final RoutingHandler routingHandler = new RoutingHandler() |         final RoutingHandler routingHandler = new RoutingHandler() | ||||||
|                 .get(CreateHandler.PATH_TEMPLATE, new CreateHandler(config.maxMazeHeight(), config.maxMazeWidth())) |                 .get(CreateHandler.PATH_TEMPLATE, new CreateHandler(config.maxMazeHeight(), config.maxMazeWidth())) | ||||||
|                 .post(RenderV1Handler.PATH_TEMPLATE, new RenderV1Handler()) |                 .post(RenderV1Handler.PATH_TEMPLATE, new RenderV1Handler()) | ||||||
|                 .post(RenderV2Handler.PATH_TEMPLATE, new RenderV2Handler()) |                 .post(RenderV2Handler.PATH_TEMPLATE, new RenderV2Handler()); | ||||||
|                 .post(RenderV3Handler.PATH_TEMPLATE, new RenderV3Handler()) |  | ||||||
|                 .post(RenderVxHandler.PATH_TEMPLATE, new RenderVxHandler()); |  | ||||||
| 
 | 
 | ||||||
|         this.undertow = Undertow.builder() |         this.undertow = Undertow.builder() | ||||||
|                 .addHttpListener(port, hostAddress) |                 .addHttpListener(port, hostAddress) | ||||||
|  |  | ||||||
|  | @ -7,7 +7,6 @@ import ch.fritteli.maze.generator.renderer.pdf.PDFRenderer; | ||||||
| import ch.fritteli.maze.generator.renderer.text.TextRenderer; | import ch.fritteli.maze.generator.renderer.text.TextRenderer; | ||||||
| import ch.fritteli.maze.generator.serialization.v1.SerializerDeserializerV1; | import ch.fritteli.maze.generator.serialization.v1.SerializerDeserializerV1; | ||||||
| import ch.fritteli.maze.generator.serialization.v2.SerializerDeserializerV2; | import ch.fritteli.maze.generator.serialization.v2.SerializerDeserializerV2; | ||||||
| import ch.fritteli.maze.generator.serialization.v3.SerializerDeserializerV3; |  | ||||||
| import io.vavr.collection.List; | import io.vavr.collection.List; | ||||||
| import io.vavr.collection.Stream; | import io.vavr.collection.Stream; | ||||||
| import io.vavr.control.Option; | import io.vavr.control.Option; | ||||||
|  | @ -24,8 +23,7 @@ public enum OutputType { | ||||||
|             maze -> TextRenderer.newInstance().render(maze).getBytes(StandardCharsets.UTF_8), |             maze -> TextRenderer.newInstance().render(maze).getBytes(StandardCharsets.UTF_8), | ||||||
|             false, |             false, | ||||||
|             "t", |             "t", | ||||||
|             "text", |             "text"), | ||||||
|             "txt"), |  | ||||||
|     HTML("text/html", |     HTML("text/html", | ||||||
|             "html", |             "html", | ||||||
|             maze -> HTMLRenderer.newInstance().render(maze).getBytes(StandardCharsets.UTF_8), |             maze -> HTMLRenderer.newInstance().render(maze).getBytes(StandardCharsets.UTF_8), | ||||||
|  | @ -67,13 +65,7 @@ public enum OutputType { | ||||||
|             SerializerDeserializerV2::serialize, |             SerializerDeserializerV2::serialize, | ||||||
|             true, |             true, | ||||||
|             "v", |             "v", | ||||||
|             "binaryv2"), |             "binaryv2"); | ||||||
|     BINARY_V3("application/octet-stream", |  | ||||||
|             "maz3", |  | ||||||
|             SerializerDeserializerV3::serialize, |  | ||||||
|             true, |  | ||||||
|             "3", |  | ||||||
|             "binaryv3"); |  | ||||||
|     @Getter |     @Getter | ||||||
|     @NotNull |     @NotNull | ||||||
|     private final String contentType; |     private final String contentType; | ||||||
|  |  | ||||||
|  | @ -68,7 +68,7 @@ public class CreateHandler extends AbstractHttpHandler { | ||||||
|                             .put(HttpString.tryFromString("X-Maze-ID"), String.valueOf(maze.getRandomSeed())) |                             .put(HttpString.tryFromString("X-Maze-ID"), String.valueOf(maze.getRandomSeed())) | ||||||
|                             .put(HttpString.tryFromString("X-Maze-Width"), String.valueOf(maze.getWidth())) |                             .put(HttpString.tryFromString("X-Maze-Width"), String.valueOf(maze.getWidth())) | ||||||
|                             .put(HttpString.tryFromString("X-Maze-Height"), String.valueOf(maze.getHeight())) |                             .put(HttpString.tryFromString("X-Maze-Height"), String.valueOf(maze.getHeight())) | ||||||
|                             .put(HttpString.tryFromString("X-Maze-Algorithm"), maze.getAlgorithm()) |                             .put(HttpString.tryFromString("X-Maze-Algorithm"), generatedMaze.generatorName()) | ||||||
|                             .put(HttpString.tryFromString("X-Maze-Generation-Duration-millis"), String.valueOf(durationMillis)); |                             .put(HttpString.tryFromString("X-Maze-Generation-Duration-millis"), String.valueOf(durationMillis)); | ||||||
|                     if (outputType.isAttachment()) { |                     if (outputType.isAttachment()) { | ||||||
|                         exchange.getResponseHeaders() |                         exchange.getResponseHeaders() | ||||||
|  |  | ||||||
|  | @ -1,9 +1,8 @@ | ||||||
| package ch.fritteli.maze.server.handler; | package ch.fritteli.maze.server.handler; | ||||||
| 
 | 
 | ||||||
| import ch.fritteli.maze.generator.algorithm.MazeGeneratorAlgorithm; | import ch.fritteli.maze.generator.algorithm.RandomDepthFirst; | ||||||
| import ch.fritteli.maze.generator.model.Maze; | import ch.fritteli.maze.generator.model.Maze; | ||||||
| import ch.fritteli.maze.generator.model.Position; | import ch.fritteli.maze.generator.model.Position; | ||||||
| import ch.fritteli.maze.server.Algorithm; |  | ||||||
| import ch.fritteli.maze.server.InvalidRequestParameterException; | import ch.fritteli.maze.server.InvalidRequestParameterException; | ||||||
| import ch.fritteli.maze.server.OutputType; | import ch.fritteli.maze.server.OutputType; | ||||||
| import io.vavr.collection.Stream; | import io.vavr.collection.Stream; | ||||||
|  | @ -34,7 +33,6 @@ class ParametersToMazeExtractor { | ||||||
|         final Option<Long> id = getParameterValue(RequestParameter.ID); |         final Option<Long> id = getParameterValue(RequestParameter.ID); | ||||||
|         final Option<Position> start = getParameterValue(RequestParameter.START); |         final Option<Position> start = getParameterValue(RequestParameter.START); | ||||||
|         final Option<Position> end = getParameterValue(RequestParameter.END); |         final Option<Position> end = getParameterValue(RequestParameter.END); | ||||||
|         final Option<Algorithm> algorithm = getParameterValue(RequestParameter.ALGORITHM); |  | ||||||
| 
 | 
 | ||||||
|         if (output.isEmpty()) { |         if (output.isEmpty()) { | ||||||
|             return Try.failure(new InvalidRequestParameterException("Path parameter %s is required and must be one of: %s".formatted( |             return Try.failure(new InvalidRequestParameterException("Path parameter %s is required and must be one of: %s".formatted( | ||||||
|  | @ -79,11 +77,8 @@ class ParametersToMazeExtractor { | ||||||
|             } else { |             } else { | ||||||
|                 maze = new Maze(desiredWidth, desiredHeight, id.getOrElse(() -> new Random().nextLong())); |                 maze = new Maze(desiredWidth, desiredHeight, id.getOrElse(() -> new Random().nextLong())); | ||||||
|             } |             } | ||||||
| 
 |             new RandomDepthFirst(maze).run(); | ||||||
|             final MazeGeneratorAlgorithm generator = algorithm.getOrElse(Algorithm.WILSON) |             return new GeneratedMaze(maze, output.get(), RandomDepthFirst.class.getSimpleName()); | ||||||
|                     .createAlgorithm(maze); |  | ||||||
|             generator.run(); |  | ||||||
|             return new GeneratedMaze(maze, output.get()); |  | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -92,6 +87,6 @@ class ParametersToMazeExtractor { | ||||||
|         return parameter.getParameterValue(this.queryParameters); |         return parameter.getParameterValue(this.queryParameters); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public record GeneratedMaze(@NotNull Maze maze, @NotNull OutputType outputType) { |     public record GeneratedMaze(@NotNull Maze maze, @NotNull OutputType outputType, @NotNull String generatorName) { | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,61 +0,0 @@ | ||||||
| package ch.fritteli.maze.server.handler; |  | ||||||
| 
 |  | ||||||
| import ch.fritteli.maze.generator.model.Maze; |  | ||||||
| import ch.fritteli.maze.generator.serialization.v3.SerializerDeserializerV3; |  | ||||||
| import ch.fritteli.maze.server.OutputType; |  | ||||||
| import io.undertow.server.HttpServerExchange; |  | ||||||
| import io.undertow.util.HeaderValues; |  | ||||||
| import io.undertow.util.Headers; |  | ||||||
| import io.undertow.util.StatusCodes; |  | ||||||
| import lombok.extern.slf4j.Slf4j; |  | ||||||
| import org.jetbrains.annotations.NotNull; |  | ||||||
| 
 |  | ||||||
| import java.nio.ByteBuffer; |  | ||||||
| 
 |  | ||||||
| @Slf4j |  | ||||||
| public class RenderV3Handler extends AbstractHttpHandler { |  | ||||||
| 
 |  | ||||||
|     public static final String PATH_TEMPLATE = "/render/v3/{output}"; |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     public void handle(@NotNull final HttpServerExchange exchange) { |  | ||||||
|         log.debug("Handling render request"); |  | ||||||
| 
 |  | ||||||
|         if (exchange.isInIoThread()) { |  | ||||||
|             exchange.dispatch(this); |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|         exchange.getRequestReceiver().receiveFullBytes((httpServerExchange, bytes) -> { |  | ||||||
|             final OutputType output = this.getOutputType(httpServerExchange); |  | ||||||
|             final byte[] render; |  | ||||||
|             try { |  | ||||||
|                 final Maze maze = SerializerDeserializerV3.deserialize(bytes); |  | ||||||
|                 render = output.render(maze); |  | ||||||
|             } catch (final Exception e) { |  | ||||||
|                 log.error("Error rendering binary maze data", e); |  | ||||||
|                 httpServerExchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR) |  | ||||||
|                         .getResponseSender() |  | ||||||
|                         .send("Error rendering maze: %s".formatted(e.getMessage())); |  | ||||||
|                 return; |  | ||||||
|             } |  | ||||||
|             httpServerExchange |  | ||||||
|                     .setStatusCode(StatusCodes.OK) |  | ||||||
|                     .getResponseHeaders() |  | ||||||
|                     .put(Headers.CONTENT_TYPE, output.getContentType()); |  | ||||||
|             httpServerExchange.getResponseSender() |  | ||||||
|                     .send(ByteBuffer.wrap(render)); |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @NotNull |  | ||||||
|     private OutputType getOutputType(@NotNull final HttpServerExchange httpServerExchange) { |  | ||||||
|         return RequestParameter.OUTPUT.<OutputType>getParameterValue(httpServerExchange.getQueryParameters()) |  | ||||||
|                 .getOrElse(() -> { |  | ||||||
|                     final HeaderValues accept = httpServerExchange.getRequestHeaders().get(Headers.ACCEPT); |  | ||||||
|                     if (accept.contains(OutputType.HTML.getContentType())) { |  | ||||||
|                         return OutputType.HTML; |  | ||||||
|                     } |  | ||||||
|                     return OutputType.TEXT_PLAIN; |  | ||||||
|                 }); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,91 +0,0 @@ | ||||||
| package ch.fritteli.maze.server.handler; |  | ||||||
| 
 |  | ||||||
| import ch.fritteli.maze.generator.model.Maze; |  | ||||||
| import ch.fritteli.maze.generator.serialization.MazeConstants; |  | ||||||
| import ch.fritteli.maze.generator.serialization.v1.SerializerDeserializerV1; |  | ||||||
| import ch.fritteli.maze.generator.serialization.v2.SerializerDeserializerV2; |  | ||||||
| import ch.fritteli.maze.generator.serialization.v3.SerializerDeserializerV3; |  | ||||||
| import ch.fritteli.maze.server.OutputType; |  | ||||||
| import io.undertow.server.HttpHandler; |  | ||||||
| import io.undertow.server.HttpServerExchange; |  | ||||||
| import io.undertow.util.HeaderValues; |  | ||||||
| import io.undertow.util.Headers; |  | ||||||
| import io.undertow.util.StatusCodes; |  | ||||||
| import lombok.extern.slf4j.Slf4j; |  | ||||||
| import org.jetbrains.annotations.NotNull; |  | ||||||
| 
 |  | ||||||
| import java.nio.ByteBuffer; |  | ||||||
| 
 |  | ||||||
| @Slf4j |  | ||||||
| public class RenderVxHandler implements HttpHandler { |  | ||||||
|     public static final String PATH_TEMPLATE = "/render/dyn/{output}"; |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     public void handleRequest(@NotNull final HttpServerExchange exchange) { |  | ||||||
|         log.debug("Handling render request"); |  | ||||||
| 
 |  | ||||||
|         if (exchange.isInIoThread()) { |  | ||||||
|             exchange.dispatch(this); |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|         exchange.getRequestReceiver().receiveFullBytes((httpServerExchange, bytes) -> { |  | ||||||
|             final OutputType output = this.getOutputType(httpServerExchange); |  | ||||||
|             final byte[] render; |  | ||||||
|             try { |  | ||||||
|                 final Version version = this.getVersion(bytes); |  | ||||||
|                 final Maze maze = switch (version) { |  | ||||||
|                     case V1 -> SerializerDeserializerV1.deserialize(bytes); |  | ||||||
|                     case V2 -> SerializerDeserializerV2.deserialize(bytes); |  | ||||||
|                     case V3 -> SerializerDeserializerV3.deserialize(bytes); |  | ||||||
|                 }; |  | ||||||
|                 render = output.render(maze); |  | ||||||
|             } catch (final Exception e) { |  | ||||||
|                 log.error("Error rendering binary maze data", e); |  | ||||||
|                 httpServerExchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR) |  | ||||||
|                         .getResponseSender() |  | ||||||
|                         .send("Error rendering maze: %s".formatted(e.getMessage())); |  | ||||||
|                 return; |  | ||||||
|             } |  | ||||||
|             httpServerExchange |  | ||||||
|                     .setStatusCode(StatusCodes.OK) |  | ||||||
|                     .getResponseHeaders() |  | ||||||
|                     .put(Headers.CONTENT_TYPE, output.getContentType()); |  | ||||||
|             httpServerExchange.getResponseSender() |  | ||||||
|                     .send(ByteBuffer.wrap(render)); |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @NotNull |  | ||||||
|     private Version getVersion(@NotNull final byte[] bytes) throws IllegalArgumentException { |  | ||||||
|         if (bytes.length < 3) { |  | ||||||
|             throw new IllegalArgumentException("Invalid input: too short"); |  | ||||||
|         } |  | ||||||
|         if (bytes[0] == MazeConstants.MAGIC_BYTE_1 && bytes[1] == MazeConstants.MAGIC_BYTE_2) { |  | ||||||
|             final byte version = bytes[2]; |  | ||||||
|             return switch (version) { |  | ||||||
|                 case SerializerDeserializerV1.VERSION_BYTE -> Version.V1; |  | ||||||
|                 case SerializerDeserializerV2.VERSION_BYTE -> Version.V2; |  | ||||||
|                 case SerializerDeserializerV3.VERSION_BYTE -> Version.V3; |  | ||||||
|                 default -> throw new IllegalArgumentException("Invalid version: " + version); |  | ||||||
|             }; |  | ||||||
|         } else { |  | ||||||
|             throw new IllegalArgumentException("Invalid input: not a Maze file"); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @NotNull |  | ||||||
|     private OutputType getOutputType(@NotNull final HttpServerExchange httpServerExchange) { |  | ||||||
|         return RequestParameter.OUTPUT.<OutputType>getParameterValue(httpServerExchange.getQueryParameters()) |  | ||||||
|                 .getOrElse(() -> { |  | ||||||
|                     final HeaderValues accept = httpServerExchange.getRequestHeaders().get(Headers.ACCEPT); |  | ||||||
|                     if (accept.contains(OutputType.HTML.getContentType())) { |  | ||||||
|                         return OutputType.HTML; |  | ||||||
|                     } |  | ||||||
|                     return OutputType.TEXT_PLAIN; |  | ||||||
|                 }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private enum Version { |  | ||||||
|         V1, V2, V3; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,7 +1,6 @@ | ||||||
| package ch.fritteli.maze.server.handler; | package ch.fritteli.maze.server.handler; | ||||||
| 
 | 
 | ||||||
| import ch.fritteli.maze.generator.model.Position; | import ch.fritteli.maze.generator.model.Position; | ||||||
| import ch.fritteli.maze.server.Algorithm; |  | ||||||
| import ch.fritteli.maze.server.OutputType; | import ch.fritteli.maze.server.OutputType; | ||||||
| import io.vavr.Tuple2; | import io.vavr.Tuple2; | ||||||
| import io.vavr.collection.HashMap; | import io.vavr.collection.HashMap; | ||||||
|  | @ -45,9 +44,7 @@ enum RequestParameter { | ||||||
|                 return new Position(x, y); |                 return new Position(x, y); | ||||||
|             }) |             }) | ||||||
|             .toOption() |             .toOption() | ||||||
|             .onEmpty(() -> log.debug("Unparseable value for parameter 'end': '{}'", p)), "e", "end"), |             .onEmpty(() -> log.debug("Unparseable value for parameter 'end': '{}'", p)), "e", "end"); | ||||||
|     ALGORITHM(p -> Algorithm.ofString(p) |  | ||||||
|             .onEmpty(() -> log.debug("Unparseable value for parameter 'algorithm': '{}'", p)), "a", "algorithm"); |  | ||||||
|     @NotNull |     @NotNull | ||||||
|     private final Function<String, Option<?>> extractor; |     private final Function<String, Option<?>> extractor; | ||||||
|     @Getter |     @Getter | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue