All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.sitebricks.client.transport.JacksonJsonTransportWithConverters Maven / Gradle / Ivy

There is a newer version: 0.8.11
Show newest version
package com.google.sitebricks.client.transport;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Set;

import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.JsonToken;
import org.codehaus.jackson.map.DeserializationContext;
import org.codehaus.jackson.map.JsonDeserializer;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.deser.CustomDeserializerFactory;
import org.codehaus.jackson.map.deser.StdDeserializerProvider;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.common.primitives.Primitives;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.google.sitebricks.conversion.Converter;
import com.google.sitebricks.conversion.ConverterRegistry;
import com.google.sitebricks.conversion.StandardTypeConverter;
import com.google.sitebricks.conversion.generics.Generics;

/**
 * @author Dhanji R. Prasanna ([email protected])
 * @author John Patterson ([email protected])
 * @author JRodriguez
 */
@Singleton
public class JacksonJsonTransportWithConverters extends Json {

  private final ObjectMapper objectMapper;
  private Collection> exceptions = Sets.newHashSet();
  
  @Inject
  public JacksonJsonTransportWithConverters(ConverterRegistry registry) {
    this.objectMapper = new ObjectMapper();
    CustomDeserializerFactory deserializerFactory = new CustomDeserializerFactory();
    
    // leave these for Jackson to handle
    exceptions.add(String.class);
    exceptions.add(Object.class);
    exceptions.addAll(Primitives.allWrapperTypes());

    // 
    Multimap typeToConverterDirection = ArrayListMultimap.create();
    addConverterDirections(registry, true, typeToConverterDirection);
    addConverterDirections(registry, false, typeToConverterDirection);
    createJacksonDeserializers(deserializerFactory, typeToConverterDirection);
    
    objectMapper.setDeserializerProvider(new StdDeserializerProvider(deserializerFactory));
  }
  
  public ObjectMapper getObjectMapper() {
	return objectMapper;
  }
  
  // keep track of which direction we want to use
  private static class ConverterDirection
  {
    Converter converter;
    boolean forward;
  }

  private void addConverterDirections(ConverterRegistry registry, boolean forward, Multimap typeToConverterDirections) {
    Multimap> typeToConverters = forward ? registry.getConvertersByTarget() : registry.getConvertersBySource();
    Set types = typeToConverters.keySet();
    for (Type type : types) {
      if (exceptions.contains(type)) continue;
      Collection> converters = typeToConverters.get(type);
      for (Converter converter : converters) {
        ConverterDirection converterDirection = new ConverterDirection();
        converterDirection.converter = converter;
        converterDirection.forward = forward;
        typeToConverterDirections.put(type, converterDirection);
      }
    }
  }
  
  private void createJacksonDeserializers(CustomDeserializerFactory deserializerFactory, Multimap typeToConverterDirections)
  {
    Set targetTypes = typeToConverterDirections.keySet();
    for (Type targetType : targetTypes) {
      Collection converterDirections = typeToConverterDirections.get(targetType);
      Class targetClass = Generics.erase(targetType);
      ConvertersDeserializer jds = new ConvertersDeserializer(converterDirections);
      typesafeAddMapping(targetClass, jds, deserializerFactory);
    }
  }

  @SuppressWarnings("unchecked")
  private  void typesafeAddMapping(Class type, JsonDeserializer deserializer,
      CustomDeserializerFactory factory) {
    factory.addSpecificMapping((Class) type, deserializer);
  }

  public  T in(InputStream in, Class type) throws IOException {
    return objectMapper.readValue(in, type);
  }

  public  void out(OutputStream out, Class type, T data) {
    try {
      objectMapper.writeValue(out, data);
    }
    catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  public class ConvertersDeserializer extends JsonDeserializer {

    private final Collection converterDirections;

    public ConvertersDeserializer(Collection converterDirections) {
      this.converterDirections = converterDirections;
    }

    public Object deserialize(JsonParser jp, DeserializationContext ctxt) 
        throws IOException, JsonProcessingException {

      Object source = getSourceObject(jp, ctxt);
      
      for (ConverterDirection converterDirection : converterDirections) {
        
        Type sourceType = converterDirection.forward ? 
            StandardTypeConverter.sourceType(converterDirection.converter) : 
            StandardTypeConverter.targetType(converterDirection.converter);
            
        // assume that Jackson only gives us non-generic types
        Class converterSourceClass = Generics.erase(sourceType);
        
        if (converterSourceClass.isAssignableFrom(source.getClass())) {
          return converterDirection.forward ? 
              StandardTypeConverter.typeSafeTo(converterDirection.converter, source) : 
              StandardTypeConverter.typeSafeFrom(converterDirection.converter, source); 
        }
      }

      throw new IllegalStateException("Cannot convert from " + source);
    }

    private Object getSourceObject(JsonParser jp, DeserializationContext ctxt) throws JsonParseException, IOException {
        JsonToken t = jp.getCurrentToken();
        if (t == JsonToken.VALUE_NUMBER_INT) {
          return jp.getLongValue();
        }
        else if (t == JsonToken.VALUE_NUMBER_FLOAT) {
          return jp.getDoubleValue();
        }
        else if (t == JsonToken.VALUE_TRUE) {
          return Boolean.TRUE;
        }
        else if (t == JsonToken.VALUE_FALSE) {
            return Boolean.FALSE;
        }
        else if (t == JsonToken.VALUE_STRING) {
          return jp.getText();
        }
        else throw new IllegalStateException();
    }
  }
}