Search code examples
javaoraclewebservernettyhelidon

java.lang.IllegalStateException: No reader found for type: class io.helidon.examples.quickstart.se.pokemon.Pokemon


I am new to Helidon and i am trying to create a basic CRUD REST service using Helidon SE. I have been referring the DbClient examples in GitHib (https://github.com/oracle/helidon/tree/master/examples/dbclient) to create a basic CRUD REST Service.

I am able to do Read all/one and Delete all/one in DB but unable to do Create or Update Operation, below is the error stack which i get when trying to invoke a POST Service :

java.util.concurrent.ExecutionException: Unhandled 'cause' of this exception encountered.
        at io.helidon.webserver.RequestRouting$RoutedRequest.defaultHandler(RequestRouting.java:394)
        at io.helidon.webserver.RequestRouting$RoutedRequest.nextNoCheck(RequestRouting.java:374)
        at io.helidon.webserver.RequestRouting$RoutedRequest.next(RequestRouting.java:417)
        at io.helidon.webserver.Handler.lambda$create$4(Handler.java:99)
        at java.base/java.util.concurrent.CompletableFuture.uniExceptionally(CompletableFuture.java:986)
        at java.base/java.util.concurrent.CompletableFuture.uniExceptionallyStage(CompletableFuture.java:1004)
        at java.base/java.util.concurrent.CompletableFuture.exceptionally(CompletableFuture.java:2307)
        at java.base/java.util.concurrent.CompletableFuture.exceptionally(CompletableFuture.java:143)
        at io.helidon.common.reactive.CompletionAwaitable.exceptionally(CompletionAwaitable.java:293)
        at io.helidon.webserver.Handler.lambda$create$5(Handler.java:97)
        at io.helidon.webserver.RequestRouting$RoutedRequest.next(RequestRouting.java:320)
        at io.helidon.metrics.MetricsSupport$MetricsContextHandler.accept(MetricsSupport.java:619)
        at io.helidon.webserver.RequestRouting$RoutedRequest.next(RequestRouting.java:320)
        at io.helidon.metrics.MetricsSupport.lambda$configureVendorMetrics$7(MetricsSupport.java:364)
        at io.helidon.webserver.RequestRouting$RoutedRequest.next(RequestRouting.java:320)
        at io.helidon.webserver.WebTracingConfig$RequestSpanHandler.accept(WebTracingConfig.java:247)
        at io.helidon.webserver.RequestRouting$RoutedRequest.next(RequestRouting.java:320)
        at io.helidon.common.context.Contexts.runInContext(Contexts.java:98)
        at io.helidon.webserver.RequestRouting.route(RequestRouting.java:87)
        at io.helidon.webserver.ForwardingHandler.channelRead0(ForwardingHandler.java:167)
        at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:99)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
        at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:324)
        at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:311)
        at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:425)
        at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:276)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
        at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
        at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
        at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163)
        at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:714)
        at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:650)
        at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:576)
        at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493)
        at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
        at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
        at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
        at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.lang.IllegalStateException: No reader found for type: class io.helidon.examples.quickstart.se.pokemon.Pokemon
        at io.helidon.media.common.MessageBodyReaderContext.readerNotFound(MessageBodyReaderContext.java:338)
        at io.helidon.media.common.MessageBodyReaderContext.unmarshall(MessageBodyReaderContext.java:167)
        at io.helidon.media.common.MessageBodyReadableContent.as(MessageBodyReadableContent.java:117)
        at io.helidon.webserver.Handler.lambda$create$5(Handler.java:83)
        ... 34 more

Below is the code written by

Main.java

package io.helidon.examples.quickstart.se;

import io.helidon.config.Config;
import io.helidon.config.ConfigValue;
import io.helidon.dbclient.DbClient;
import io.helidon.examples.quickstart.se.pokemon.PokemonService;
import io.helidon.health.HealthSupport;
import io.helidon.health.checks.HealthChecks;
import io.helidon.media.jsonp.JsonpSupport;
import io.helidon.metrics.MetricsSupport;
import io.helidon.webserver.Routing;
import io.helidon.webserver.WebServer;

import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.logging.LogManager;

/**
 * The application main class.
 */
public final class Main {

    /**
     * Cannot be instantiated.
     */
    private Main() {
    }

    /**
     * Application main entry point.
     * @param args command line arguments.
     * @throws IOException if there are problems reading logging properties
     */
    public static void main(final String[] args) throws IOException {
        startServer();
    }

    /**
     * Start the server.
     * @return the created {@link WebServer} instance
     * @throws IOException if there are problems reading logging properties
     */
    static WebServer startServer() throws IOException {
        // load logging configuration
        setupLogging();
        System.out.println("Logging Set up");

        // By default this will pick up application.yaml from the classpath
        Config config = Config.create();
        System.out.println("Config created");

        // Build server with JSONP support
        WebServer server = WebServer.builder(createRouting(config))
                .config(config.get("server"))
                .addMediaSupport(JsonpSupport.create())
                .build();

        System.out.println("Webserver Created : "+server);

        // Try to start the server. If successful, print some info and arrange to
        // print a message at shutdown. If unsuccessful, print the exception.
        System.out.println("Server startup initiating");
        server.start()
                .thenAccept(ws -> {
                    System.out.println(
                            "WEB server is up! http://localhost:" + ws.port() + "/greet");
                    ws.whenShutdown().thenRun(()
                            -> System.out.println("WEB server is DOWN. Good bye!"));
                })
                .exceptionally(t -> {
                    System.err.println("Startup failed: " + t.getMessage());
                    t.printStackTrace(System.err);
                    return null;
                });

        // Server threads are not daemon. No need to block. Just react.

        return server;
    }

    /**
     * Creates new {@link Routing}.
     *
     * @return routing configured with JSON support, a health check, and a service
     * @param config configuration of this server
     */
    private static Routing createRouting(Config config) {
        System.out.println("Inside create Routing.");
        Config dbConfig = config.get("db");
        System.out.println("dbConfig : ");
        System.out.println(dbConfig.get("db.source").asString().orElse("No Data"));

        ConfigValue<Map<String, String>> test = dbConfig.asMap();
        
//        for (Map.Entry<String,String> entry : test)
//            System.out.println("Key = " + entry.getKey() +
//                    ", Value = " + entry.getValue());
        //Client services are added through a service loader
        DbClient dbClient = DbClient.builder(dbConfig).build();
        System.out.println("dbClient : "+dbClient);

        MetricsSupport metrics = MetricsSupport.create();
        GreetService greetService = new GreetService(config);
        PokemonService pokemonService = new PokemonService(dbClient);
        HealthSupport health = HealthSupport.builder()
                .addLiveness(HealthChecks.healthChecks())   // Adds a convenient set of checks
                .build();
        System.out.println("Returning Value");

        return Routing.builder()
                .register(health)                   // Health at "/health"
                .register(metrics)                  // Metrics at "/metrics"
                .register("/greet", greetService)
                .register("/pokemon", pokemonService)
                .build();
    }

    /**
     * Configure logging from logging.properties file.
     */
    private static void setupLogging() throws IOException {
        try (InputStream is = Main.class.getResourceAsStream("/logging.properties")) {
            LogManager.getLogManager().readConfiguration(is);
        }
    }
}

PokemonService.java

package io.helidon.examples.quickstart.se.pokemon;

import io.helidon.common.http.Http;
import io.helidon.common.reactive.Multi;
import io.helidon.dbclient.DbClient;
import io.helidon.dbclient.DbRow;
import io.helidon.webserver.*;

import javax.json.JsonObject;
import java.util.concurrent.CompletionException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;

public class PokemonService implements Service {

    private static final Logger LOGGER = Logger.getLogger(PokemonService.class.getName());
    private final AtomicReference<String> greeting = new AtomicReference<>();

    private final DbClient dbClient;

    public PokemonService(DbClient dbClient){
        this.dbClient = dbClient;
        greeting.set("Hello Pokemon");
    }

    @Override
    public void update(Routing.Rules rules) {
        rules
                .get("/", this::listPokemons)
                // get one
                .get("/{name}", this::getPokemon)
                // create new
                .post("/", Handler.create(Pokemon.class, this::insertPokemon))
                // delete all
                .delete("/", this::deleteAllPokemons)
                // delete one
                .delete("/{name}", this::deletePokemon)
        ;
    }

    private void listPokemons(ServerRequest request, ServerResponse response) {
        Multi<JsonObject> rows = dbClient.execute(exec -> exec.namedQuery("select-all"))
                .map(it -> it.as(JsonObject.class));

        response.send(rows, JsonObject.class);
    }

    private void insertPokemon(ServerRequest request, ServerResponse response, Pokemon pokemon) {
        System.out.println("Start of insertPokemon");
        System.out.println("pokemon : ");
        System.out.println(pokemon);
        System.out.println("Name : "+pokemon.getName());
        System.out.println("Type : "+pokemon.getType());
        dbClient.execute(exec -> exec
                .createNamedInsert("insert2")
                .namedParam(pokemon)
                .execute())
                .thenAccept(count -> response.send("Inserted: " + count + " values"))
                .exceptionally(throwable -> sendError(throwable, response));
    }

    private <T> T sendError(Throwable throwable, ServerResponse response) {
        Throwable realCause = throwable;
        if (throwable instanceof CompletionException) {
            realCause = throwable.getCause();
        }
        response.status(Http.Status.INTERNAL_SERVER_ERROR_500);
        response.send("Failed to process request: " + realCause.getClass().getName() + "(" + realCause.getMessage() + ")");
        LOGGER.log(Level.WARNING, "Failed to process request", throwable);
        return null;
    }

    private void getPokemon(ServerRequest request, ServerResponse response) {
        String pokemonName = request.path().param("name");

        dbClient.execute(exec -> exec.namedGet("select-one", pokemonName))
                .thenAccept(opt -> opt.ifPresentOrElse(it -> sendRow(it, response),
                        () -> sendNotFound(response, "Pokemon "
                                + pokemonName
                                + " not found")))
                .exceptionally(throwable -> sendError(throwable, response));
    }

    private void sendRow(DbRow row, ServerResponse response) {
        response.send(row.as(JsonObject.class));
    }

    private void sendNotFound(ServerResponse response, String message) {
        response.status(Http.Status.NOT_FOUND_404);
        response.send(message);
    }

    private void deleteAllPokemons(ServerRequest request, ServerResponse response) {
        dbClient.execute(exec -> exec
                // this is to show how ad-hoc statements can be executed (and their naming in Tracing and Metrics)
                .createDelete("DELETE FROM pokemons")
                .execute())
                .thenAccept(count -> response.send("Deleted: " + count + " values"))
                .exceptionally(throwable -> sendError(throwable, response));
    }

    private void deletePokemon(ServerRequest request, ServerResponse response) {
        final String name = request.path().param("name");

        dbClient.execute(exec -> exec.namedDelete("delete", name))
                .thenAccept(count -> response.send("Deleted: " + count + " values"))
                .exceptionally(throwable -> sendError(throwable, response));
    }
}

Pokemon.java

package io.helidon.examples.quickstart.se.pokemon;

import io.helidon.common.Reflected;

@Reflected
public class Pokemon {
    private String name;
    private String type;

    /**
     * Default constructor.
     */
    public Pokemon() {
        // JSON-B
    }

    /**
     * Create pokemon with name and type.
     *
     * @param name name of the beast
     * @param type type of the beast
     */
    public Pokemon(String name, String type) {
        this.name = name;
        this.type = type;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }
}

PokemonMapper.java

package io.helidon.examples.quickstart.se.pokemon;

import io.helidon.dbclient.DbColumn;
import io.helidon.dbclient.DbMapper;
import io.helidon.dbclient.DbRow;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Maps database statements to {@link io.helidon.examples.quickstart.se.pokemon.Pokemon} class.
 */
public class PokemonMapper implements DbMapper<Pokemon> {

    @Override
    public Pokemon read(DbRow row) {
//        DbColumn id = row.column("id");
        DbColumn name = row.column("name");
        DbColumn type = row.column("type");
        return new Pokemon(name.as(String.class), type.as(String.class));
    }

    @Override
    public Map<String, Object> toNamedParameters(Pokemon value) {
        Map<String, Object> map = new HashMap<>(2);
//        map.put("id", value.getId());
        map.put("name", value.getName());
        map.put("type", value.getType());
        return map;
    }

    @Override
    public List<Object> toIndexedParameters(Pokemon value) {
        List<Object> list = new ArrayList<>(2);
//        list.add(value.getId());
        list.add(value.getName());
        list.add(value.getType());
        return list;
    }
}

PokemonMapperProvider.java

package io.helidon.examples.quickstart.se.pokemon;

import io.helidon.dbclient.DbMapper;
import io.helidon.dbclient.spi.DbMapperProvider;

import javax.annotation.Priority;
import java.util.Optional;

/**
 * Provides pokemon mappers.
 */
@Priority(1000)
public class PokemonMapperProvider implements DbMapperProvider {
    private static final PokemonMapper MAPPER = new PokemonMapper();

    @SuppressWarnings("unchecked")
    @Override
    public <T> Optional<DbMapper<T>> mapper(Class<T> type) {
        if (type.equals(Pokemon.class)) {
            return Optional.of((DbMapper<T>) MAPPER);
        }
        return Optional.empty();
    }
}

Any help in this regard is truly appreciated.

Regards, Gaurav


Solution

  • The Pokemon class is a Java Bean and can be converted from/to JSON with JSON-B; that is what the original example does.

    You need to register the Jsonb media support:

    .addMediaSupport(JsonbSupport.create())
    

    This also requires the following Maven dependency:

    <dependency>
        <groupId>io.helidon.media</groupId>
        <artifactId>helidon-media-jsonb</artifactId>
    </dependency>