graphql-java で 雑に pagination 実装したい

実装した graphql-java で雑に pagination 実装できるようにした - 宇宙行きたい


なんかサクッと返してくれるのないかなぁと探したんだけどなかった。 SimpleListConnection は全部のリスト渡さないといけないし。。。ということで雑に書いたんだけど、これをそのまま使いたいってよりもこういうことしたいんだけど自分で実装しないでもなんか汎用的なのあるんでしょ!?俺が見つけられてないだけで!!! 教えてください!!!的な気持ちで書いてみた。(出てこなかったらこれもうちょっと弄って使うけど)

    var count = hogeMapper.count();
    var hogeConnection = new SimpleConnection<Hoge>(count, (offset, limit) -> {
      return hogeMapper.getHoges(offset, limit);
    }).get(env);

こんな感じでページネーションしたいリストの合計サイズと offset, limit を元に list 返す関数渡すと graphql.relay.Connectionインスタンス返してくれるやつ

import static graphql.Assert.assertNotNull;

import com.google.common.base.Objects;
import com.google.common.io.BaseEncoding;
import graphql.relay.Connection;
import graphql.relay.ConnectionCursor;
import graphql.relay.DefaultConnection;
import graphql.relay.DefaultEdge;
import graphql.relay.DefaultPageInfo;
import graphql.relay.Edge;
import graphql.schema.DataFetchingEnvironment;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.BiFunction;

public class SimpleConnection<T> {

  final SimpleConnectionCursor sizeCursor;
  final BiFunction<Integer, Integer, List<T> > func;

  public SimpleConnection(Integer size, BiFunction<Integer, Integer, List<T> > func) {
    this.sizeCursor = new SimpleConnectionCursor(assertNotNull(size));
    this.func = assertNotNull(func);
  }

  public Connection<T> get(DataFetchingEnvironment environment){
    Integer firstInteger = environment.getArgument("first");
    if(firstInteger == null){
      firstInteger = 20;
    }
    var limitCursor = new SimpleConnectionCursor(firstInteger);
    var offsetCursor = new SimpleConnectionCursor(
      (String) environment.getArgument("after"));
    if(offsetCursor.getOffset() != 0){
      offsetCursor = new SimpleConnectionCursor(offsetCursor.getOffset() + 1);
    }

    List<T> list = func.apply(offsetCursor.getOffset(), limitCursor.getOffset());
    if(list.isEmpty()){
      return emptyConnection();
    }

    List<Edge<T>> edges = new ArrayList<>();
    int index = offsetCursor.getOffset();
    for(T o : list){
      edges.add(new DefaultEdge<>(o, new SimpleConnectionCursor(index++)));
    }

    var firstEdge = edges.get(0);
    var lastEdge = edges.get(edges.size() - 1);
    var pageInfo = new DefaultPageInfo(
      firstEdge.getCursor(),
      lastEdge.getCursor(),
      0 < ((SimpleConnectionCursor)firstEdge.getCursor()).getOffset(),
      ((SimpleConnectionCursor)lastEdge.getCursor()).getOffset() < sizeCursor.getOffset() -1
    );
    return new DefaultConnection<>(
      edges,
      pageInfo
    );
  }

  Connection<T> emptyConnection() {
    return new DefaultConnection<>(Collections.emptyList(), new DefaultPageInfo(null, null, false, false));
  }

  static class SimpleConnectionCursor implements ConnectionCursor{
    static final String PREFIX = "DUMMY";
    final int offset;

    SimpleConnectionCursor(int offset) {
      this.offset = offset;
    }

    SimpleConnectionCursor(String cursor) {
      this.offset = convertToOffset(cursor);
    }

    /**
     * @return an opaque string that represents this cursor.
     */
    @Override
    public String getValue() {
      return convertToCursorString(offset);
    }

    public int getOffset(){
      return offset;
    }

    private int convertToOffset(String cursorString) {
      if (cursorString == null) {
        return 0;
      }
      try{
        var string = new String(BaseEncoding.base64().decode(cursorString), StandardCharsets.UTF_8);
        return Integer.parseInt(string.substring(PREFIX.length()));
      }catch (IllegalArgumentException ignored){
      }
      return 0;
    }

    private String convertToCursorString(int offset) {
      return BaseEncoding.base64().encode((PREFIX + Integer.toString(offset)).getBytes(StandardCharsets.UTF_8));
    }

    @Override
    public boolean equals(Object o) {
      if (this == o) {
        return true;
      }
      if (o == null || getClass() != o.getClass()) {
        return false;
      }
      SimpleConnectionCursor that = (SimpleConnectionCursor) o;
      return offset == that.offset;
    }

    @Override
    public int hashCode() {
      return Objects.hashCode(offset);
    }
  }
}