Last active
February 15, 2021 08:44
-
-
Save minwoox/5b58e75664bccff29c63cfde080f90f9 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package example.armeria.server.annotated; | |
import java.util.Map; | |
import java.util.concurrent.CompletableFuture; | |
import java.util.concurrent.ConcurrentHashMap; | |
import java.util.concurrent.atomic.AtomicInteger; | |
import javax.annotation.Nullable; | |
import com.fasterxml.jackson.annotation.JsonProperty; | |
import com.fasterxml.jackson.core.JsonProcessingException; | |
import com.fasterxml.jackson.databind.JsonNode; | |
import com.fasterxml.jackson.databind.ObjectMapper; | |
import com.linecorp.armeria.common.AggregatedHttpRequest; | |
import com.linecorp.armeria.common.HttpRequest; | |
import com.linecorp.armeria.common.HttpResponse; | |
import com.linecorp.armeria.common.HttpStatus; | |
import com.linecorp.armeria.common.MediaType; | |
import com.linecorp.armeria.server.HttpService; | |
import com.linecorp.armeria.server.ServiceRequestContext; | |
import com.linecorp.armeria.server.annotation.Delete; | |
import com.linecorp.armeria.server.annotation.Get; | |
import com.linecorp.armeria.server.annotation.Param; | |
import com.linecorp.armeria.server.annotation.Post; | |
import com.linecorp.armeria.server.annotation.ProducesJson; | |
import com.linecorp.armeria.server.annotation.Put; | |
public class BlogService { | |
private static final ObjectMapper mapper = new ObjectMapper(); | |
private final AtomicInteger idGenerator = new AtomicInteger(); | |
private final Map<Integer, Blog> blogs = new ConcurrentHashMap<>(); | |
@Post("/blogs") | |
public HttpResponse createBlog(@Param String title, @Param String content) throws JsonProcessingException { | |
// Use integer for simplicity. | |
final int id = idGenerator.getAndIncrement(); | |
final Blog blog = new Blog(id, title, content); | |
// Use just a map to store the blog. In real world, we should use a database. | |
// If we use a blocking call to the database, we should use @Blocking. | |
blogs.put(id, blog); | |
// We can add additional property such as url: "http://tutorial.com/blogs/1" to respect Rest API. | |
final byte[] bytes = mapper.writeValueAsBytes(blog); | |
return HttpResponse.of(HttpStatus.OK, MediaType.JSON_UTF_8, bytes); | |
} | |
@Get("/blogs/:id") | |
public HttpResponse getBlog(@Param int id) throws JsonProcessingException { | |
final Blog blog = blogs.get(id); | |
final byte[] content = mapper.writeValueAsBytes(blog); | |
return HttpResponse.of(HttpStatus.OK, MediaType.JSON_UTF_8, content); | |
} | |
@Get("/blogs") | |
@ProducesJson | |
public Iterable<Blog> getBlogs() throws JsonProcessingException { | |
return blogs.values(); | |
} | |
@Put("/blogs/:id") | |
public HttpResponse updateBlog(@Param int id, @Param String content) throws JsonProcessingException { | |
final Blog blog = blogs.get(id); | |
if (blog == null) { | |
return HttpResponse.of(HttpStatus.NOT_FOUND); | |
} | |
final Blog updated = new Blog(id, blog.title(), content); | |
blogs.put(id, updated); | |
final byte[] bytes = mapper.writeValueAsBytes(blog); | |
return HttpResponse.of(HttpStatus.OK, MediaType.JSON_UTF_8, bytes); | |
} | |
@Delete("/blogs/:id") | |
public HttpResponse removeBlog(@Param int id) { | |
final Blog removed = blogs.remove(id); | |
if (removed == null) { | |
return HttpResponse.of(HttpStatus.NOT_FOUND); | |
} | |
return HttpResponse.of(HttpStatus.NO_CONTENT); | |
} | |
// Binds to @Put("/blogs/:id") using route API. | |
private static class UpdateBlogService implements HttpService { | |
private final Map<Integer, Blog> blogs; | |
UpdateBlogService(Map<Integer, Blog> blogs) { | |
this.blogs = blogs; | |
} | |
@Override | |
public HttpResponse serve(ServiceRequestContext ctx, HttpRequest req) throws Exception { | |
final CompletableFuture<HttpResponse> future = new CompletableFuture<>(); | |
req.aggregate().thenAccept(aggregatedHttpRequest -> { | |
final int id = Integer.parseInt(ctx.pathParam("id")); | |
final String content = content(aggregatedHttpRequest); | |
if (content == null) { | |
future.completeExceptionally(new IllegalArgumentException("content is not specified.")); | |
} | |
final Blog blog = blogs.get(id); | |
if (blog == null) { | |
future.complete(HttpResponse.of(HttpStatus.NOT_FOUND)); | |
return; | |
} | |
final Blog updated = new Blog(id, blog.title(), content); | |
blogs.put(id, updated); | |
future.complete(HttpResponse.of(HttpStatus.OK)); | |
}); | |
return HttpResponse.from(future); | |
} | |
@Nullable | |
private static String content(AggregatedHttpRequest aggregatedHttpRequest) { | |
final JsonNode jsonNode; | |
try { | |
jsonNode = mapper.readTree(aggregatedHttpRequest.contentUtf8()); | |
} catch (JsonProcessingException e) { | |
return null; | |
} | |
final JsonNode content = jsonNode.get("content"); | |
if (content == null) { | |
return null; | |
} | |
return content.asText(); | |
} | |
} | |
// Add some comment that you can use a library that removes the boiler plates code such as lombok | |
static final class Blog { | |
private final int id; | |
private final String title; | |
private final String content; | |
private final long createdAt; | |
Blog(int id, String title, String content) { | |
this.id = id; | |
this.title = title; | |
this.content = content; | |
createdAt = System.currentTimeMillis(); | |
} | |
@JsonProperty("id") | |
int id() { | |
return id; | |
} | |
@JsonProperty("title") | |
String title() { | |
return title; | |
} | |
@JsonProperty("content") | |
String content() { | |
return content; | |
} | |
@JsonProperty("createdAt") | |
long createdAt() { | |
return createdAt; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment