/** * Copyright 2013-2014 David Rusek <dave dot rusek at gmail dot com> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.robotninjas.barge.jaxrs; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.core.Version; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider; import com.google.common.collect.Lists; import jersey.repackaged.com.google.common.collect.Iterables; import org.robotninjas.barge.api.*; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.List; /** * Utility methods for configuring Jackson. */ public abstract class Jackson { /** * @return a new Object mapper with configured deserializer for barge' object model. */ public static ObjectMapper objectMapper() { ObjectMapper mapper = new ObjectMapper(); SimpleModule raftMessagesModule = new SimpleModule("MyModule", new Version(0, 1, 0, null, "org.robotninjas", "barge")) .addDeserializer(RequestVote.class, new RequestVoteDeserializer()) .addDeserializer(HttpClusterConfig.class, new HttpClusterConfigDeserializer()) .addDeserializer(HttpReplica.class, new HttpReplicaDeserializer()) .addDeserializer(RequestVoteResponse.class, new RequestVoteResponseDeserializer()) .addDeserializer(AppendEntries.class, new AppendEntriesDeserializer()) .addDeserializer(AppendEntriesResponse.class, new AppendEntriesResponseDeserializer()); mapper.registerModule(raftMessagesModule); return mapper; } /** * @return a suitable provider with configured deserialization objects. */ public static JacksonJaxbJsonProvider customJacksonProvider() { JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider(); provider.setMapper(objectMapper()); return provider; } private static class RequestVoteDeserializer extends StdDeserializer<RequestVote> { protected RequestVoteDeserializer() { super(RequestVote.class); } /** * @see <a href="http://www.cowtowncoder.com/blog/archives/2009/01/entry_132.html">Jackson Documentation</a> */ @Override public RequestVote deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { RequestVote.Builder builder = RequestVote.newBuilder(); while (jsonParser.nextToken() != JsonToken.END_OBJECT) { String fieldName = jsonParser.getCurrentName(); jsonParser.nextToken(); switch (fieldName) { case "term": builder.setTerm(jsonParser.getLongValue()); break; case "candidateId": builder.setCandidateId(jsonParser.getText()); break; case "lastLogIndex": builder.setLastLogIndex(jsonParser.getLongValue()); break; case "lastLogTerm": builder.setLastLogTerm(jsonParser.getLongValue()); break; } } return builder.build(); } } private static class RequestVoteResponseDeserializer extends StdDeserializer<RequestVoteResponse> { public RequestVoteResponseDeserializer() { super(RequestVoteResponse.class); } /** * @see <a href="http://www.cowtowncoder.com/blog/archives/2009/01/entry_132.html">Jackson Documentation</a> */ @Override public RequestVoteResponse deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { RequestVoteResponse.Builder builder = RequestVoteResponse.newBuilder(); while (jsonParser.nextToken() != JsonToken.END_OBJECT) { String fieldName = jsonParser.getCurrentName(); jsonParser.nextToken(); if (fieldName.equals("term")) { builder.setTerm(jsonParser.getLongValue()); } else if (fieldName.equals("voteGranted")) { builder.setVoteGranted(jsonParser.getBooleanValue()); } } return builder.build(); } } private static class AppendEntriesDeserializer extends StdDeserializer<AppendEntries> { public AppendEntriesDeserializer() { super(AppendEntries.class); } @Override public AppendEntries deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { AppendEntries.Builder builder = AppendEntries.newBuilder(); while (jsonParser.nextToken() != JsonToken.END_OBJECT) { String fieldName = jsonParser.getCurrentName(); JsonToken token = jsonParser.nextToken(); switch (fieldName) { case "term": builder.setTerm(jsonParser.getLongValue()); break; case "prevLogIndex": builder.setPrevLogIndex(jsonParser.getLongValue()); break; case "prevLogTerm": builder.setPrevLogTerm(jsonParser.getLongValue()); break; case "commitIndex": builder.setCommitIndex(jsonParser.getLongValue()); break; case "leaderId": builder.setLeaderId(jsonParser.getText()); break; case "entriesList": if (token != JsonToken.START_ARRAY) { throw new IOException("entriesList should be an array, got " + token); } while (jsonParser.nextToken() != JsonToken.END_ARRAY) { builder.addEntry(deserializeEntry(jsonParser)); } break; } } return builder.build(); } private Entry deserializeEntry(JsonParser jsonParser) throws IOException { Entry.Builder builder = Entry.newBuilder(); while (jsonParser.nextToken() != JsonToken.END_OBJECT) { String fieldName = jsonParser.getCurrentName(); jsonParser.nextToken(); if (fieldName.equals("term")) { builder.setTerm(jsonParser.getLongValue()); } else if (fieldName.equals("command")) { builder.setCommand(jsonParser.getBinaryValue()); } } return builder.build(); } } private static class AppendEntriesResponseDeserializer extends StdDeserializer<AppendEntriesResponse> { protected AppendEntriesResponseDeserializer() { super(AppendEntriesResponse.class); } @Override public AppendEntriesResponse deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { AppendEntriesResponse.Builder builder = AppendEntriesResponse.newBuilder(); while (jsonParser.nextToken() != JsonToken.END_OBJECT) { String fieldName = jsonParser.getCurrentName(); jsonParser.nextToken(); switch (fieldName) { case "term": builder.setTerm(jsonParser.getLongValue()); break; case "success": builder.setSuccess(jsonParser.getBooleanValue()); break; case "lastLogIndex": builder.setLastLogIndex(jsonParser.getLongValue()); break; } } return builder.build(); } } private static class HttpClusterConfigDeserializer extends JsonDeserializer<HttpClusterConfig> { @Override public HttpClusterConfig deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException, JsonProcessingException { List<HttpReplica> replicas = Lists.newArrayList(); while (jsonParser.nextToken() != JsonToken.END_OBJECT) { String fieldName = jsonParser.getCurrentName(); JsonToken token = jsonParser.nextToken(); switch (fieldName) { case "cluster": if (token != JsonToken.START_ARRAY) { throw new IOException("cluster should be an array, got " + token); } while (jsonParser.nextToken() != JsonToken.END_ARRAY) { replicas.add(jsonParser.readValueAs(HttpReplica.class)); } break; } } return new HttpClusterConfig(replicas.get(0), Iterables.toArray(Iterables.skip(replicas, 1), HttpReplica.class)); } } private static class HttpReplicaDeserializer extends JsonDeserializer<HttpReplica> { @Override public HttpReplica deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException, JsonProcessingException { URI uri = null; while (jsonParser.nextToken() != JsonToken.END_OBJECT) { String fieldName = jsonParser.getCurrentName(); jsonParser.nextToken(); switch (fieldName) { case "uri": String uriString = jsonParser.getValueAsString(); try { uri = new URI(uriString); } catch (URISyntaxException e) { throw new IllegalStateException("failed to build proper URI from parsed uri field " + uriString); } break; } } return new HttpReplica(uri); } } }