/** * 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; } } protected final ExBeanSerializerModifier modifier = new ExBeanSerializerModifier(); protected final ThreadLocal<String> namespaceClass = new ThreadLocal<String>(); protected final ThreadLocal<AckCallback<?>> currentAckClass = new ThreadLocal<AckCallback<?>>(); protected final ObjectMapper objectMapper = new ObjectMapper(); protected final EventDeserializer eventDeserializer = new EventDeserializer(); protected final AckArgsDeserializer ackArgsDeserializer = new AckArgsDeserializer(); protected 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(); } }