com.corundumstudio.socketio.protocol.JacksonJsonSupport.java Source code

Java tutorial

Introduction

Here is the source code for com.corundumstudio.socketio.protocol.JacksonJsonSupport.java

Source

/**
 * Copyright 2012 Nikita Koksharov
 *
 * 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 com.corundumstudio.socketio.protocol;

import io.netty.buffer.ByteBufInputStream;
import io.netty.buffer.ByteBufOutputStream;
import io.netty.util.internal.PlatformDependent;

import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.corundumstudio.socketio.AckCallback;
import com.corundumstudio.socketio.MultiTypeAckCallback;
import com.corundumstudio.socketio.namespace.Namespace;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonArrayFormatVisitor;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatTypes;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.fasterxml.jackson.databind.type.ArrayType;

public class JacksonJsonSupport implements JsonSupport {

    private class AckArgsDeserializer extends StdDeserializer<AckArgs> {

        private static final long serialVersionUID = 7810461017389946707L;

        protected AckArgsDeserializer() {
            super(AckArgs.class);
        }

        @Override
        public AckArgs deserialize(JsonParser jp, DeserializationContext ctxt)
                throws IOException, JsonProcessingException {
            List<Object> args = new ArrayList<Object>();
            AckArgs result = new AckArgs(args);

            ObjectMapper mapper = (ObjectMapper) jp.getCodec();
            JsonNode root = mapper.readTree(jp);
            AckCallback<?> callback = currentAckClass.get();
            Iterator<JsonNode> iter = root.iterator();
            int i = 0;
            while (iter.hasNext()) {
                Object val;

                Class<?> clazz = callback.getResultClass();
                if (callback instanceof MultiTypeAckCallback) {
                    MultiTypeAckCallback multiTypeAckCallback = (MultiTypeAckCallback) callback;
                    clazz = multiTypeAckCallback.getResultClasses()[i];
                }

                JsonNode arg = iter.next();
                if (arg.isTextual() || arg.isBoolean()) {
                    clazz = Object.class;
                }

                val = mapper.treeToValue(arg, clazz);
                args.add(val);
                i++;
            }
            return result;
        }

    }

    public static class EventKey {

        private String namespaceName;
        private String eventName;

        public EventKey(String namespaceName, String eventName) {
            super();
            this.namespaceName = namespaceName;
            this.eventName = eventName;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((eventName == null) ? 0 : eventName.hashCode());
            result = prime * result + ((namespaceName == null) ? 0 : namespaceName.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            EventKey other = (EventKey) obj;
            if (eventName == null) {
                if (other.eventName != null)
                    return false;
            } else if (!eventName.equals(other.eventName))
                return false;
            if (namespaceName == null) {
                if (other.namespaceName != null)
                    return false;
            } else if (!namespaceName.equals(other.namespaceName))
                return false;
            return true;
        }

    }

    private class EventDeserializer extends StdDeserializer<Event> {

        private static final long serialVersionUID = 8178797221017768689L;

        final Map<EventKey, List<Class<?>>> eventMapping = PlatformDependent.newConcurrentHashMap();

        protected EventDeserializer() {
            super(Event.class);
        }

        @Override
        public Event deserialize(JsonParser jp, DeserializationContext ctxt)
                throws IOException, JsonProcessingException {
            ObjectMapper mapper = (ObjectMapper) jp.getCodec();
            String eventName = jp.nextTextValue();

            EventKey ek = new EventKey(namespaceClass.get(), eventName);
            if (!eventMapping.containsKey(ek)) {
                ek = new EventKey(Namespace.DEFAULT_NAME, eventName);
                if (!eventMapping.containsKey(ek)) {
                    return new Event(eventName, Collections.emptyList());
                }
            }

            List<Object> eventArgs = new ArrayList<Object>();
            Event event = new Event(eventName, eventArgs);
            List<Class<?>> eventClasses = eventMapping.get(ek);
            int i = 0;
            while (true) {
                JsonToken token = jp.nextToken();
                if (token == JsonToken.END_ARRAY) {
                    break;
                }
                if (i > eventClasses.size() - 1) {
                    log.debug("Event {} has more args than declared in handler: {}", eventName, null);
                    break;
                }
                Class<?> eventClass = eventClasses.get(i);
                Object arg = mapper.readValue(jp, eventClass);
                eventArgs.add(arg);
                i++;
            }
            return event;
        }

    }

    public static class ByteArraySerializer extends StdSerializer<byte[]> {

        private static final long serialVersionUID = 3420082888596468148L;

        private final ThreadLocal<List<byte[]>> arrays = new ThreadLocal<List<byte[]>>() {
            @Override
            protected List<byte[]> initialValue() {
                return new ArrayList<byte[]>();
            };
        };

        public ByteArraySerializer() {
            super(byte[].class);
        }

        @Override
        public boolean isEmpty(byte[] value) {
            return (value == null) || (value.length == 0);
        }

        @Override
        public void serialize(byte[] value, JsonGenerator jgen, SerializerProvider provider)
                throws IOException, JsonGenerationException {
            Map<String, Object> map = new HashMap<String, Object>();
            map.put("num", arrays.get().size());
            map.put("_placeholder", true);
            jgen.writeObject(map);
            arrays.get().add(value);
        }

        @Override
        public void serializeWithType(byte[] value, JsonGenerator jgen, SerializerProvider provider,
                TypeSerializer typeSer) throws IOException, JsonGenerationException {
            serialize(value, jgen, provider);
        }

        @Override
        public JsonNode getSchema(SerializerProvider provider, Type typeHint) {
            ObjectNode o = createSchemaNode("array", true);
            ObjectNode itemSchema = createSchemaNode("string"); //binary values written as strings?
            return o.set("items", itemSchema);
        }

        @Override
        public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint)
                throws JsonMappingException {
            if (visitor != null) {
                JsonArrayFormatVisitor v2 = visitor.expectArrayFormat(typeHint);
                if (v2 != null) {
                    v2.itemsFormat(JsonFormatTypes.STRING);
                }
            }
        }

        public List<byte[]> getArrays() {
            return arrays.get();
        }

        public void clear() {
            arrays.set(new ArrayList<byte[]>());
        }

    }

    private class ExBeanSerializerModifier extends BeanSerializerModifier {

        private final ByteArraySerializer serializer = new ByteArraySerializer();

        @Override
        public JsonSerializer<?> modifyArraySerializer(SerializationConfig config, ArrayType valueType,
                BeanDescription beanDesc, JsonSerializer<?> serializer) {
            if (valueType.getRawClass().equals(byte[].class)) {
                return this.serializer;
            }

            return super.modifyArraySerializer(config, valueType, beanDesc, serializer);
        }

        public ByteArraySerializer getSerializer() {
            return serializer;
        }

    }

    private final ExBeanSerializerModifier modifier = new ExBeanSerializerModifier();
    private final ThreadLocal<String> namespaceClass = new ThreadLocal<String>();
    private final ThreadLocal<AckCallback<?>> currentAckClass = new ThreadLocal<AckCallback<?>>();
    private final ObjectMapper objectMapper = new ObjectMapper();
    private final EventDeserializer eventDeserializer = new EventDeserializer();
    private final AckArgsDeserializer ackArgsDeserializer = new AckArgsDeserializer();

    private static final Logger log = LoggerFactory.getLogger(JacksonJsonSupport.class);

    public JacksonJsonSupport() {
        this(new Module[] {});
    }

    public JacksonJsonSupport(Module... modules) {
        if (modules != null && modules.length > 0) {
            objectMapper.registerModules(modules);
        }
        init(objectMapper);
    }

    protected void init(ObjectMapper objectMapper) {
        SimpleModule module = new SimpleModule();
        module.setSerializerModifier(modifier);
        module.addDeserializer(Event.class, eventDeserializer);
        module.addDeserializer(AckArgs.class, ackArgsDeserializer);
        objectMapper.registerModule(module);

        objectMapper.setSerializationInclusion(Include.NON_NULL);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        objectMapper.configure(SerializationFeature.WRITE_BIGDECIMAL_AS_PLAIN, true);
        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
    }

    @Override
    public void addEventMapping(String namespaceName, String eventName, Class<?>... eventClass) {
        eventDeserializer.eventMapping.put(new EventKey(namespaceName, eventName), Arrays.asList(eventClass));
    }

    @Override
    public void removeEventMapping(String namespaceName, String eventName) {
        eventDeserializer.eventMapping.remove(new EventKey(namespaceName, eventName));
    }

    @Override
    public <T> T readValue(String namespaceName, ByteBufInputStream src, Class<T> valueType) throws IOException {
        namespaceClass.set(namespaceName);
        return objectMapper.readValue(src, valueType);
    }

    @Override
    public AckArgs readAckArgs(ByteBufInputStream src, AckCallback<?> callback) throws IOException {
        currentAckClass.set(callback);
        return objectMapper.readValue(src, AckArgs.class);
    }

    @Override
    public void writeValue(ByteBufOutputStream out, Object value) throws IOException {
        modifier.getSerializer().clear();
        objectMapper.writeValue(out, value);
    }

    @Override
    public List<byte[]> getArrays() {
        return modifier.getSerializer().getArrays();
    }

}