/**
* JBoss, Home of Professional Open Source
* Copyright Red Hat, Inc., and individual contributors.
*
* 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.jboss.aerogear.simplepush.protocol.impl.json;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.jboss.aerogear.simplepush.protocol.Ack;
import org.jboss.aerogear.simplepush.protocol.AckMessage;
import org.jboss.aerogear.simplepush.protocol.HelloMessage;
import org.jboss.aerogear.simplepush.protocol.HelloResponse;
import org.jboss.aerogear.simplepush.protocol.MessageType;
import org.jboss.aerogear.simplepush.protocol.NotificationMessage;
import org.jboss.aerogear.simplepush.protocol.PingMessage;
import org.jboss.aerogear.simplepush.protocol.RegisterMessage;
import org.jboss.aerogear.simplepush.protocol.RegisterResponse;
import org.jboss.aerogear.simplepush.protocol.UnregisterMessage;
import org.jboss.aerogear.simplepush.protocol.UnregisterResponse;
import org.jboss.aerogear.simplepush.protocol.impl.AckImpl;
import org.jboss.aerogear.simplepush.protocol.impl.AckMessageImpl;
import org.jboss.aerogear.simplepush.protocol.impl.HelloMessageImpl;
import org.jboss.aerogear.simplepush.protocol.impl.HelloResponseImpl;
import org.jboss.aerogear.simplepush.protocol.impl.NotificationMessageImpl;
import org.jboss.aerogear.simplepush.protocol.impl.PingMessageImpl;
import org.jboss.aerogear.simplepush.protocol.impl.RegisterMessageImpl;
import org.jboss.aerogear.simplepush.protocol.impl.RegisterResponseImpl;
import org.jboss.aerogear.simplepush.protocol.impl.StatusImpl;
import org.jboss.aerogear.simplepush.protocol.impl.UnregisterMessageImpl;
import org.jboss.aerogear.simplepush.protocol.impl.UnregisterResponseImpl;
import java.io.IOException;
import java.io.StringWriter;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
/**
* JSON utility class for transforming SimplePush messages to and from JSON.
*
*/
public final class JsonUtil {
private static ObjectMapper om = createObjectMapper();
private static ObjectMapper createObjectMapper() {
om = new ObjectMapper();
final SimpleModule module = new SimpleModule("MyModule", new Version(0, 10, 0, null,
"org.jboss.aerogear", "aerogear-simplepush-server"));
module.addDeserializer(MessageType.class, new MessageTypeDeserializer());
module.addDeserializer(RegisterMessageImpl.class, new RegisterDeserializer());
module.addSerializer(RegisterMessageImpl.class, new RegisterSerializer());
module.addDeserializer(RegisterResponseImpl.class, new RegisterResponseDeserializer());
module.addSerializer(RegisterResponseImpl.class, new RegisterResponseSerializer());
module.addDeserializer(HelloMessageImpl.class, new HelloDeserializer());
module.addSerializer(HelloMessageImpl.class, new HelloSerializer());
module.addDeserializer(HelloResponseImpl.class, new HelloResponseDeserializer());
module.addSerializer(HelloResponseImpl.class, new HelloResponseSerializer());
module.addDeserializer(AckMessageImpl.class, new AckDeserializer());
module.addSerializer(AckMessageImpl.class, new AckSerializer());
module.addDeserializer(PingMessageImpl.class, new PingDeserializer());
module.addSerializer(PingMessageImpl.class, new PingSerializer());
module.addDeserializer(NotificationMessageImpl.class, new NotificationDeserializer());
module.addSerializer(NotificationMessageImpl.class, new NotificationSerializer());
module.addDeserializer(UnregisterMessageImpl.class, new UnregisterDeserializer());
module.addSerializer(UnregisterMessageImpl.class, new UnregisterMessageSerializer());
module.addDeserializer(UnregisterResponseImpl.class, new UnregisterResponseDeserializer());
module.addSerializer(UnregisterResponseImpl.class, new UnregisterResponseSerializer());
om.registerModule(module);
return om;
}
private JsonUtil() {
}
/**
* Transforms from JSON to the type specified.
*
* @param json the json to be transformed.
* @param type the Java type that the JSON should be transformed to.
* @return T an instance of the type populated with data from the json message.
*/
public static <T> T fromJson(final String json, final Class<T> type) {
try {
return om.readValue(json, type);
} catch (final Exception e) {
throw new RuntimeException("error trying to parse json [" + json + "]", e);
}
}
/**
* Transforms from Java object notation to JSON.
*
* @param obj the Java object to transform into JSON.
* @return {@code String} the json representation for the object.
*/
public static String toJson(final Object obj) {
final StringWriter stringWriter = new StringWriter();
try {
om.writeValue(stringWriter, obj);
return stringWriter.toString();
} catch (final Exception e) {
throw new RuntimeException("error trying to parse json [" + obj + "]", e);
}
}
/**
* A convenience method for transforming a SimplePush message frame into an instance
* of {@link MessageType}.
*
* @param json the JSON representing a message in the SimplePush protocol
* @return {@link MessageType} an instance of MessageType for the passed in json.
*/
public static MessageType parseFrame(final String json) {
return fromJson(json, MessageType.class);
}
private static class RegisterDeserializer extends JsonDeserializer<RegisterMessageImpl> {
@Override
public RegisterMessageImpl deserialize(final JsonParser jp, final DeserializationContext ctxt)
throws IOException {
final ObjectCodec oc = jp.getCodec();
final JsonNode node = oc.readTree(jp);
return new RegisterMessageImpl(node.get(RegisterMessage.CHANNEL_ID_FIELD).asText());
}
}
private static class RegisterSerializer extends JsonSerializer<RegisterMessage> {
@Override
public void serialize(final RegisterMessage register, final JsonGenerator jgen,
final SerializerProvider provider) throws IOException {
jgen.writeStartObject();
jgen.writeFieldName(RegisterMessage.MESSSAGE_TYPE_FIELD);
jgen.writeString(register.getMessageType().toString().toLowerCase());
jgen.writeFieldName(RegisterMessage.CHANNEL_ID_FIELD);
jgen.writeString(register.getChannelId());
jgen.writeEndObject();
}
}
private static class RegisterResponseDeserializer extends JsonDeserializer<RegisterResponseImpl> {
@Override
public RegisterResponseImpl deserialize(final JsonParser jp, final DeserializationContext ctxt)
throws IOException {
final ObjectCodec oc = jp.getCodec();
final JsonNode node = oc.readTree(jp);
return new RegisterResponseImpl(node.get(RegisterMessage.CHANNEL_ID_FIELD).asText(),
new StatusImpl(node.get(RegisterResponseImpl.STATUS_FIELD).asInt(), "N/A"),
node.get(RegisterResponseImpl.PUSH_ENDPOINT__FIELD).asText());
}
}
private static class RegisterResponseSerializer extends JsonSerializer<RegisterResponse> {
@Override
public void serialize(final RegisterResponse registerResponse, final JsonGenerator jgen,
final SerializerProvider provider) throws IOException {
jgen.writeStartObject();
jgen.writeFieldName(RegisterResponse.MESSSAGE_TYPE_FIELD);
jgen.writeString(registerResponse.getMessageType().toString().toLowerCase());
jgen.writeFieldName(RegisterResponse.CHANNEL_ID_FIELD);
jgen.writeString(registerResponse.getChannelId());
jgen.writeFieldName(RegisterResponse.STATUS_FIELD);
jgen.writeNumber(registerResponse.getStatus().getCode());
jgen.writeFieldName(RegisterResponse.PUSH_ENDPOINT__FIELD);
jgen.writeString(registerResponse.getPushEndpoint());
jgen.writeEndObject();
}
}
private static class HelloDeserializer extends JsonDeserializer<HelloMessageImpl> {
@Override
public HelloMessageImpl deserialize(final JsonParser jp, final DeserializationContext ctxt)
throws IOException {
final ObjectCodec oc = jp.getCodec();
final JsonNode node = oc.readTree(jp);
final JsonNode channelIdsNode = node.get(HelloMessage.CHANNEL_IDS_FIELD);
final Set<String> channelIds = new HashSet<String>();
if (channelIdsNode != null && channelIdsNode.isArray()) {
for (JsonNode channelIdNode : channelIdsNode) {
channelIds.add(channelIdNode.asText());
}
}
final JsonNode uaid = node.get(HelloMessage.UAID_FIELD);
if (uaid != null) {
return new HelloMessageImpl(node.get(HelloMessage.UAID_FIELD).asText(), channelIds);
} else {
return new HelloMessageImpl();
}
}
}
private static class HelloSerializer extends JsonSerializer<HelloMessage> {
@Override
public void serialize(final HelloMessage hello, final JsonGenerator jgen,
final SerializerProvider provider) throws IOException {
jgen.writeStartObject();
jgen.writeFieldName(HelloMessage.MESSSAGE_TYPE_FIELD);
jgen.writeString(hello.getMessageType().toString().toLowerCase());
jgen.writeFieldName(HelloMessage.UAID_FIELD);
jgen.writeString(hello.getUAID());
jgen.writeArrayFieldStart(HelloMessage.CHANNEL_IDS_FIELD);
for (String channelId : hello.getChannelIds()) {
jgen.writeString(channelId);
}
jgen.writeEndArray();
jgen.writeEndObject();
}
}
private static class HelloResponseDeserializer extends JsonDeserializer<HelloResponseImpl> {
@Override
public HelloResponseImpl deserialize(final JsonParser jp, final DeserializationContext ctxt)
throws IOException {
final ObjectCodec oc = jp.getCodec();
final JsonNode node = oc.readTree(jp);
final JsonNode uaid = node.get(HelloMessage.UAID_FIELD);
return new HelloResponseImpl(UUID.fromString(uaid.asText()).toString());
}
}
private static class HelloResponseSerializer extends JsonSerializer<HelloResponse> {
@Override
public void serialize(final HelloResponse helloResponse, final JsonGenerator jgen,
final SerializerProvider provider) throws IOException {
jgen.writeStartObject();
jgen.writeFieldName(HelloMessage.MESSSAGE_TYPE_FIELD);
jgen.writeString(helloResponse.getMessageType().toString().toLowerCase());
jgen.writeFieldName(HelloMessage.UAID_FIELD);
jgen.writeString(helloResponse.getUAID());
jgen.writeEndObject();
}
}
private static class AckDeserializer extends JsonDeserializer<AckMessageImpl> {
@Override
public AckMessageImpl deserialize(final JsonParser jp, final DeserializationContext ctxt) throws IOException {
final ObjectCodec oc = jp.getCodec();
final JsonNode node = oc.readTree(jp);
final JsonNode acksNode = node.get(AckMessage.UPDATES_FIELD);
final Set<Ack> acks = new HashSet<Ack>();
if (acksNode.isArray()) {
for (JsonNode ackNode : acksNode) {
acks.add(new AckImpl(ackNode.get("channelID").asText(), ackNode.get("version").asLong()));
}
}
return new AckMessageImpl(acks);
}
}
private static class AckSerializer extends JsonSerializer<AckMessage> {
@Override
public void serialize(final AckMessage ackMessage, final JsonGenerator jgen,
final SerializerProvider provider) throws IOException {
jgen.writeStartObject();
jgen.writeFieldName(AckMessage.MESSSAGE_TYPE_FIELD);
jgen.writeString(ackMessage.getMessageType().toString().toLowerCase());
jgen.writeArrayFieldStart(AckMessage.UPDATES_FIELD);
for (Ack ack : ackMessage.getAcks()) {
jgen.writeStartObject();
jgen.writeFieldName("channelID");
jgen.writeString(ack.getChannelId());
jgen.writeFieldName(AckMessage.VERSION_FIELD);
jgen.writeNumber(ack.getVersion());
jgen.writeEndObject();
}
jgen.writeEndArray();
jgen.writeEndObject();
}
}
private static class NotificationDeserializer extends JsonDeserializer<NotificationMessageImpl> {
@Override
public NotificationMessageImpl deserialize(final JsonParser jp, final DeserializationContext ctxt)
throws IOException {
final ObjectCodec oc = jp.getCodec();
final JsonNode node = oc.readTree(jp);
final JsonNode updatesNode = node.get(NotificationMessage.UPDATES_FIELD);
final Set<Ack> acks = new HashSet<Ack>();
if (updatesNode.isArray()) {
for (JsonNode channelNode : updatesNode) {
final JsonNode versionNode = channelNode.get(NotificationMessage.VERSION_FIELD);
final JsonNode channelIdNode = channelNode.get(RegisterMessage.CHANNEL_ID_FIELD);
acks.add(new AckImpl(channelIdNode.asText(), versionNode.asLong()));
}
}
return new NotificationMessageImpl(acks);
}
}
private static class NotificationSerializer extends JsonSerializer<NotificationMessage> {
@Override
public void serialize(final NotificationMessage notification, final JsonGenerator jgen,
final SerializerProvider provider) throws IOException {
jgen.writeStartObject();
jgen.writeFieldName(NotificationMessage.MESSSAGE_TYPE_FIELD);
jgen.writeString(notification.getMessageType().toString().toLowerCase());
jgen.writeArrayFieldStart(NotificationMessage.UPDATES_FIELD);
for (Ack ack : notification.getAcks()) {
jgen.writeStartObject();
jgen.writeFieldName(RegisterMessage.CHANNEL_ID_FIELD);
jgen.writeString(ack.getChannelId());
jgen.writeFieldName(NotificationMessage.VERSION_FIELD);
jgen.writeNumber(ack.getVersion());
jgen.writeEndObject();
}
jgen.writeEndArray();
jgen.writeEndObject();
}
}
private static class UnregisterDeserializer extends JsonDeserializer<UnregisterMessageImpl> {
@Override
public UnregisterMessageImpl deserialize(final JsonParser jp, final DeserializationContext ctxt)
throws IOException {
final ObjectCodec oc = jp.getCodec();
final JsonNode node = oc.readTree(jp);
final JsonNode channelIdNode = node.get(RegisterMessage.CHANNEL_ID_FIELD);
return new UnregisterMessageImpl(channelIdNode.asText());
}
}
private static class UnregisterMessageSerializer extends JsonSerializer<UnregisterMessage> {
@Override
public void serialize(final UnregisterMessage unregister, final JsonGenerator jgen,
final SerializerProvider provider) throws IOException {
jgen.writeStartObject();
jgen.writeFieldName(UnregisterMessage.MESSSAGE_TYPE_FIELD);
jgen.writeString(unregister.getMessageType().toString().toLowerCase());
jgen.writeFieldName(RegisterMessage.CHANNEL_ID_FIELD);
jgen.writeString(unregister.getChannelId());
jgen.writeEndObject();
}
}
private static class MessageTypeDeserializer extends JsonDeserializer<MessageType> {
@Override
public MessageType deserialize(final JsonParser jp, final DeserializationContext ctxt) throws IOException {
final ObjectCodec oc = jp.getCodec();
final JsonNode node = oc.readTree(jp);
final JsonNode messageTypeNode = node.get(MessageType.MESSSAGE_TYPE_FIELD);
return new MessageType() {
@Override
public Type getMessageType() {
if (messageTypeNode == null) {
return MessageType.Type.PING;
}
return MessageType.Type.valueOf(messageTypeNode.asText().toUpperCase());
}
};
}
}
private static class UnregisterResponseDeserializer extends JsonDeserializer<UnregisterResponseImpl> {
@Override
public UnregisterResponseImpl deserialize(final JsonParser jp, final DeserializationContext ctxt)
throws IOException {
final ObjectCodec oc = jp.getCodec();
final JsonNode node = oc.readTree(jp);
final JsonNode channelIdNode = node.get(RegisterResponse.CHANNEL_ID_FIELD);
return new UnregisterResponseImpl(channelIdNode.asText(), new StatusImpl(node.get(UnregisterResponse.STATUS_FIELD).asInt(), "N/A"));
}
}
private static class UnregisterResponseSerializer extends JsonSerializer<UnregisterResponse> {
@Override
public void serialize(final UnregisterResponse unregisterResponse, final JsonGenerator jgen,
final SerializerProvider provider) throws IOException {
jgen.writeStartObject();
jgen.writeFieldName(RegisterResponse.MESSSAGE_TYPE_FIELD);
jgen.writeString(unregisterResponse.getMessageType().toString().toLowerCase());
jgen.writeFieldName(RegisterResponse.CHANNEL_ID_FIELD);
jgen.writeString(unregisterResponse.getChannelId());
jgen.writeFieldName(RegisterResponse.STATUS_FIELD);
jgen.writeNumber(unregisterResponse.getStatus().getCode());
jgen.writeEndObject();
}
}
private static class PingDeserializer extends JsonDeserializer<PingMessageImpl> {
@Override
public PingMessageImpl deserialize(final JsonParser jp, final DeserializationContext ctxt) throws IOException {
final ObjectCodec oc = jp.getCodec();
final JsonNode node = oc.readTree(jp);
if (node.isObject() && node.size() == 0) {
return new PingMessageImpl(node.toString());
}
throw new RuntimeException("Invalid Ping message format : [" + node.toString() + "]");
}
}
private static class PingSerializer extends JsonSerializer<PingMessage> {
@Override
public void serialize(final PingMessage ping, final JsonGenerator jgen,
final SerializerProvider provider) throws IOException {
jgen.writeStartObject();
jgen.writeEndObject();
}
}
}