package me.wener.jraphql.example; import com.github.wenerme.wava.util.Chars; import com.github.wenerme.wava.util.Inputs; import com.github.wenerme.wava.util.JSON; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Streams; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.stream.Collectors; import jodd.bean.BeanCopy; import jodd.bean.BeanUtil; import lombok.Data; import lombok.SneakyThrows; import me.wener.jraphql.exec.FieldResolveContext; import me.wener.jraphql.exec.FieldResolver; import me.wener.jraphql.exec.GraphExecuteException; import me.wener.jraphql.exec.TypeResolver; /** * @author wener * @since 2018/4/12 */ public class StarWarResolverV1 implements FieldResolver, TypeResolver { @SneakyThrows public static StarWarData loadData() { return JSON.parse(Inputs.getResourceAsString("starwar.json"), StarWarData.class); } @Override public Object resolve(FieldResolveContext ctx) { switch (ctx.getObjectName()) { case "Query": return resolveQuery(ctx); case "Mutation": switch (ctx.getFieldName()) { case "createReview": { StarWarData data = ctx.getRootSource(); CreateReviewInput createReviewInput = new CreateReviewInput(); ReviewInput input = new ReviewInput(); BeanCopy.fromMap(ctx.getArguments()).to(createReviewInput).copy(); BeanCopy.from(ctx.getArguments().get("review")).to(input).copy(); createReviewInput.setReview(input); return data.createReview(createReviewInput); } default: throw new AssertionError(); } case "Starship": return resolveStarship(ctx); case "Human": return resolveHuman(ctx); case "Droid": return resolveDroid(ctx); case "FriendsEdge": return resolveFriendsEdge(ctx); default: return BeanUtil.declared.getProperty(ctx.getSource(), ctx.getFieldName()); } } private Object resolveDroid(FieldResolveContext ctx) { StarWarData data = ctx.getRootSource(); Droid source; if (ctx.getSource() instanceof String) { source = data.getDroid(ctx.getSource()); } else { source = ctx.getSource(); } switch (ctx.getFieldName()) { case "friendsConnection": ConnectionArgument argument = new ConnectionArgument(); BeanCopy.fromMap(ctx.getArguments()).toBean(argument).copy(); return new FriendsConnection().setArgument(argument).setIds(source.getFriends()).init(); default: return BeanUtil.declared.getProperty(source, ctx.getFieldName()); } } private Object resolveHuman(FieldResolveContext ctx) { StarWarData data = ctx.getRootSource(); Human source; if (ctx.getSource() instanceof String) { source = data.getHuman(ctx.getSource()); } else { source = ctx.getSource(); } switch (ctx.getFieldName()) { case "friendsConnection": ConnectionArgument argument = new ConnectionArgument(); BeanCopy.fromMap(ctx.getArguments()).toBean(argument).copy(); return new FriendsConnection().setArgument(argument).setIds(source.getFriends()).init(); default: return BeanUtil.declared.getProperty(source, ctx.getFieldName()); } } public Object resolveQuery(FieldResolveContext ctx) { StarWarData source = ctx.getExecuteContext().getSource(); switch (ctx.getFieldName()) { case "character": return source.getCharacter((String) ctx.getArguments().get("id")); case "starship": return source.getStartship((String) ctx.getArguments().get("id")); case "human": return source.getHuman((String) ctx.getArguments().get("id")); case "droid": return source.getDroid((String) ctx.getArguments().get("id")); case "node": return source.get((String) ctx.getArguments().get("id")); case "search": return source.search((String) ctx.getArguments().get("text")); case "hero": String name = "R2-D2"; if ("EMPIRE".equals(ctx.getArguments().get("episode"))) { return source .getHumans() .stream() .filter(v -> v.getName().equals("Luke Skywalker")) .findFirst() .orElse(null); } else { return source .getDroids() .stream() .filter(v -> v.getName().equals("R2-D2")) .findFirst() .orElse(null); } case "reviews": return source.getReviews().get((String) ctx.getArguments().get("episode")); default: throw new AssertionError(); } } public Object resolveFriendsEdge(FieldResolveContext ctx) { String cursor = ctx.getSource(); switch (ctx.getFieldName()) { case "cursor": return cursor; case "node": return ctx.getRootSource().getCharacter(cursor); default: throw new AssertionError(); } } public Object resolveStarship(FieldResolveContext ctx) { Starship source; if (ctx.getSource() instanceof String) { source = ctx.getExecuteContext().getSource().getStartship(ctx.getSource()); } else { source = ctx.getSource(); } switch (ctx.getFieldName()) { case "id": return source.getId(); case "name": return source.getName(); case "length": switch ((String) ctx.getArguments().get("unit")) { case "METER": return source.getLength(); case "FOOT": return source.getLength() * 3.28084; default: throw new GraphExecuteException("invalid unit"); } default: throw new AssertionError(); } } @Override public Object resolveType(FieldResolveContext resolveContext, Object source) { if (source instanceof String) { source = resolveContext.getRootSource().get((String) source); } if (source instanceof Droid) { return "Droid"; } if (source instanceof Human) { return "Human"; } if (source instanceof Review) { return "Review"; } if (source instanceof Starship) { return "Starship"; } return null; } @Data static class Droid { private String id; private String name; private List friends; private List appearsIn; private String primaryFunction; } @Data static class Starship { private String id; private String name; private Double length; } @Data static class StarWarData { private List humans; private List droids; private List starships; private Map> reviews = Maps.newHashMap(); public Object get(String id) { Object v = getCharacter(id); if (v == null) { v = getStartship(id); } if (v == null) { v = getReview(id); } return v; } public Object getCharacter(String id) { Object v = getDroid(id); if (v == null) { v = getHuman(id); } return v; } public Starship getStartship(String id) { return starships.stream().filter(v -> v.getId().equals(id)).findFirst().orElse(null); } public Review getReview(String id) { return reviews .values() .stream() .flatMap(Collection::stream) .filter(v -> v.getId().equals(id)) .findFirst() .orElse(null); } public Droid getDroid(String id) { return droids.stream().filter(v -> v.getId().equals(id)).findFirst().orElse(null); } public Human getHuman(String id) { return humans.stream().filter(v -> v.getId().equals(id)).findFirst().orElse(null); } public Object search(String text) { return Streams.concat( humans.stream().filter(v -> Chars.containsIgnoreCase(v.getName(), text)), droids.stream().filter(v -> Chars.containsIgnoreCase(v.getName(), text)), starships.stream().filter(v -> Chars.containsIgnoreCase(v.getName(), text))) .collect(Collectors.toList()); } public Review createReview(CreateReviewInput input) { Review review = new Review(); review.setId(UUID.randomUUID().toString()); review .setStars(input.getReview().getStars()) .setCommentary(input.getReview().getCommentary()); reviews.computeIfAbsent(input.getEpisode(), (k) -> Lists.newArrayList()).add(review); return review; } } @Data static class Human implements TypeResolver { private String id; private String name; private List friends; private List appearsIn; private Double height; private Integer mass; private List starships; /** Inline the type resolve */ @Override public Object resolveType(FieldResolveContext resolveContext, Object source) { return "Human"; } } @Data static class Review { private String id; private Integer stars; private String commentary; } @Data static class PageInfo { private String startCursor; private String endCursor; private Boolean hasNextPage; } @Data static class FriendsConnection { private ConnectionArgument argument; private List ids; private List edges; private Integer totalCount; private List friends; private PageInfo pageInfo; public FriendsConnection init() { int start = 0; int page = argument.getFirst() == null ? 2 : argument.getFirst(); int end; if (argument.getAfter() != null) { start = ids.indexOf(argument.getAfter()); if (start < 0) { edges = Collections.emptyList(); // STOP } } if (start >= 0) { end = start + page; if (end >= ids.size()) { end = ids.size() - 1; } edges = ids.subList(start, end); } totalCount = edges.size(); // use the edges as friends friends = edges; pageInfo = new PageInfo() .setStartCursor(edges.isEmpty() ? null : edges.get(0)) .setEndCursor(edges.isEmpty() ? null : edges.get(edges.size() - 1)); pageInfo.setHasNextPage( pageInfo.getEndCursor() != null && ids.indexOf(pageInfo.getEndCursor()) < ids.size() - 1); return this; } } @Data static class ReviewInput { private Integer stars; private String commentary; } @Data static class CreateReviewInput { private String episode; private ReviewInput review; } @Data static class ConnectionArgument { private Integer first; private String after; } }