package org.springframework.cloud.netflix.eureka.server; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Field; import java.util.HashMap; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.Version; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.ObjectWriter; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.module.SimpleModule; import com.netflix.appinfo.DataCenterInfo; import com.netflix.appinfo.InstanceInfo; import com.netflix.appinfo.LeaseInfo; import com.netflix.discovery.converters.EurekaJacksonCodec; import com.netflix.discovery.converters.EurekaJacksonCodec.InstanceInfoDeserializer; import com.netflix.discovery.converters.EurekaJacksonCodec.InstanceInfoSerializer; import com.netflix.discovery.converters.wrappers.CodecWrappers.LegacyJacksonJson; import com.netflix.discovery.shared.Application; import com.netflix.discovery.shared.Applications; import static com.netflix.discovery.converters.wrappers.CodecWrappers.getCodecName; /** * @author Spencer Gibb */ public class CloudJacksonJson extends LegacyJacksonJson { protected final CloudJacksonCodec codec = new CloudJacksonCodec(); public CloudJacksonCodec getCodec() { return codec; } @Override public String codecName() { return getCodecName(LegacyJacksonJson.class); } @Override public <T> String encode(T object) throws IOException { return this.codec.writeToString(object); } @Override public <T> void encode(T object, OutputStream outputStream) throws IOException { this.codec.writeTo(object, outputStream); } @Override public <T> T decode(String textValue, Class<T> type) throws IOException { return this.codec.readValue(type, textValue); } @Override public <T> T decode(InputStream inputStream, Class<T> type) throws IOException { return this.codec.readValue(type, inputStream); } static class CloudJacksonCodec extends EurekaJacksonCodec { private static final Version VERSION = new Version(1, 1, 0, null, null, null); @SuppressWarnings("deprecation") public CloudJacksonCodec() { super(); ObjectMapper mapper = new ObjectMapper(); mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); SimpleModule module = new SimpleModule("eureka1.x", VERSION); module.addSerializer(DataCenterInfo.class, new DataCenterInfoSerializer()); module.addSerializer(InstanceInfo.class, new CloudInstanceInfoSerializer()); module.addSerializer(Application.class, new ApplicationSerializer()); module.addSerializer(Applications.class, new ApplicationsSerializer( this.getVersionDeltaKey(), this.getAppHashCodeKey())); module.addDeserializer(DataCenterInfo.class, new DataCenterInfoDeserializer()); module.addDeserializer(LeaseInfo.class, new LeaseInfoDeserializer()); module.addDeserializer(InstanceInfo.class, new CloudInstanceInfoDeserializer(mapper)); module.addDeserializer(Application.class, new ApplicationDeserializer(mapper)); module.addDeserializer(Applications.class, new ApplicationsDeserializer( mapper, this.getVersionDeltaKey(), this.getAppHashCodeKey())); mapper.registerModule(module); HashMap<Class<?>, ObjectReader> readers = new HashMap<>(); readers.put(InstanceInfo.class, mapper.reader().withType(InstanceInfo.class) .withRootName("instance")); readers.put(Application.class, mapper.reader().withType(Application.class) .withRootName("application")); readers.put(Applications.class, mapper.reader().withType(Applications.class) .withRootName("applications")); setField("objectReaderByClass", readers); HashMap<Class<?>, ObjectWriter> writers = new HashMap<>(); writers.put(InstanceInfo.class, mapper.writer().withType(InstanceInfo.class) .withRootName("instance")); writers.put(Application.class, mapper.writer().withType(Application.class) .withRootName("application")); writers.put(Applications.class, mapper.writer().withType(Applications.class) .withRootName("applications")); setField("objectWriterByClass", writers); setField("mapper", mapper); } void setField(String name, Object value) { Field field = ReflectionUtils.findField(EurekaJacksonCodec.class, name); ReflectionUtils.makeAccessible(field); ReflectionUtils.setField(field, this, value); } } static class CloudInstanceInfoSerializer extends InstanceInfoSerializer { @Override public void serialize(final InstanceInfo info, JsonGenerator jgen, SerializerProvider provider) throws IOException { InstanceInfo updated = updateIfNeeded(info); super.serialize(updated, jgen, provider); } } static InstanceInfo updateIfNeeded(final InstanceInfo info) { if (info.getInstanceId() == null && info.getMetadata() != null) { String instanceId = info.getMetadata().get("instanceId"); if (StringUtils.hasText(instanceId)) { // backwards compatibility for Angel if (StringUtils.hasText(info.getHostName()) && !instanceId.startsWith(info.getHostName())) { instanceId = info.getHostName()+":"+instanceId; } return new InstanceInfo.Builder(info).setInstanceId(instanceId).build(); } } return info; } static class CloudInstanceInfoDeserializer extends InstanceInfoDeserializer { protected CloudInstanceInfoDeserializer(ObjectMapper mapper) { super(mapper); } @Override public InstanceInfo deserialize(JsonParser jp, DeserializationContext context) throws IOException { InstanceInfo info = super.deserialize(jp, context); InstanceInfo updated = updateIfNeeded(info); return updated; } } }