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

com.netflix.discovery.converters.EurekaJacksonCodec Maven / Gradle / Ivy

There is a newer version: 2.0.4
Show newest version
package com.netflix.discovery.converters;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
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.ObjectReader;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.netflix.appinfo.AmazonInfo;
import com.netflix.appinfo.DataCenterInfo;
import com.netflix.appinfo.DataCenterInfo.Name;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.appinfo.InstanceInfo.ActionType;
import com.netflix.appinfo.InstanceInfo.InstanceStatus;
import com.netflix.appinfo.InstanceInfo.PortType;
import com.netflix.appinfo.LeaseInfo;
import com.netflix.discovery.DiscoveryManager;
import com.netflix.discovery.EurekaClientConfig;
import com.netflix.discovery.shared.Application;
import com.netflix.discovery.shared.Applications;
import com.netflix.discovery.util.StringCache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author Tomasz Bak
 * @author Spencer Gibb
 */
public class EurekaJacksonCodec {

    private static final Logger logger = LoggerFactory.getLogger(EurekaJacksonCodec.class);

    private static final Version VERSION = new Version(1, 1, 0, null);

    public static final String NODE_LEASE = "leaseInfo";
    public static final String NODE_METADATA = "metadata";
    public static final String NODE_DATACENTER = "dataCenterInfo";
    public static final String NODE_APP = "application";

    protected static final String ELEM_INSTANCE = "instance";
    protected static final String ELEM_OVERRIDDEN_STATUS = "overriddenstatus";
    protected static final String ELEM_HOST = "hostName";
    protected static final String ELEM_INSTANCE_ID = "instanceId";
    protected static final String ELEM_APP = "app";
    protected static final String ELEM_IP = "ipAddr";
    protected static final String ELEM_SID = "sid";
    protected static final String ELEM_STATUS = "status";
    protected static final String ELEM_PORT = "port";
    protected static final String ELEM_SECURE_PORT = "securePort";
    protected static final String ELEM_COUNTRY_ID = "countryId";
    protected static final String ELEM_IDENTIFYING_ATTR = "identifyingAttribute";
    protected static final String ELEM_HEALTHCHECKURL = "healthCheckUrl";
    protected static final String ELEM_SECHEALTHCHECKURL = "secureHealthCheckUrl";
    protected static final String ELEM_APPGROUPNAME = "appGroupName";
    protected static final String ELEM_HOMEPAGEURL = "homePageUrl";
    protected static final String ELEM_STATUSPAGEURL = "statusPageUrl";
    protected static final String ELEM_VIPADDRESS = "vipAddress";
    protected static final String ELEM_SECVIPADDRESS = "secureVipAddress";
    protected static final String ELEM_ISCOORDINATINGDISCSOERVER = "isCoordinatingDiscoveryServer";
    protected static final String ELEM_LASTUPDATEDTS = "lastUpdatedTimestamp";
    protected static final String ELEM_LASTDIRTYTS = "lastDirtyTimestamp";
    protected static final String ELEM_ACTIONTYPE = "actionType";
    protected static final String ELEM_ASGNAME = "asgName";
    protected static final String ELEM_NAME = "name";
    protected static final String DATACENTER_METADATA = "metadata";

    protected static final String VERSIONS_DELTA_TEMPLATE = "versions_delta";
    protected static final String APPS_HASHCODE_TEMPTE = "apps_hashcode";

    public static EurekaJacksonCodec INSTANCE = new EurekaJacksonCodec();

    /**
     * XStream codec supports character replacement in field names to generate XML friendly
     * names. This feature is also configurable, and replacement strings can be provided by a user.
     * To obey these rules, version and apppsHash key field names must be formatted according to the provided
     * configuration, which by default replaces '_' with '__' (double underscores).
     */
    private final String versionDeltaKey;
    private final String appHashCodeKey;

    private final ObjectMapper mapper;

    private final Map, ObjectReader> objectReaderByClass;
    private final Map, ObjectWriter> objectWriterByClass;

    public EurekaJacksonCodec() {
        this.versionDeltaKey = formatKey(VERSIONS_DELTA_TEMPLATE);
        this.appHashCodeKey = formatKey(APPS_HASHCODE_TEMPTE);
        this.mapper = new ObjectMapper();

        this.mapper.setSerializationInclusion(Include.NON_NULL);

        SimpleModule module = new SimpleModule("eureka1.x", VERSION);
        module.addSerializer(DataCenterInfo.class, new DataCenterInfoSerializer());
        module.addSerializer(InstanceInfo.class, new InstanceInfoSerializer());
        module.addSerializer(Application.class, new ApplicationSerializer());
        module.addSerializer(Applications.class, new ApplicationsSerializer(this.versionDeltaKey, this.appHashCodeKey));

        module.addDeserializer(DataCenterInfo.class, new DataCenterInfoDeserializer());
        module.addDeserializer(LeaseInfo.class, new LeaseInfoDeserializer());
        module.addDeserializer(InstanceInfo.class, new InstanceInfoDeserializer(this.mapper));
        module.addDeserializer(Application.class, new ApplicationDeserializer(this.mapper));
        module.addDeserializer(Applications.class, new ApplicationsDeserializer(this.mapper, this.versionDeltaKey, this.appHashCodeKey));

        this.mapper.registerModule(module);

        HashMap, 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"));
        this.objectReaderByClass = readers;

        HashMap, 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"));
        this.objectWriterByClass = writers;
    }

    protected ObjectMapper getMapper() {
        return mapper;
    }

    protected String getVersionDeltaKey() {
        return versionDeltaKey;
    }

    protected String getAppHashCodeKey() {
        return appHashCodeKey;
    }

    protected static String formatKey(String keyTemplate) {
        EurekaClientConfig clientConfig = DiscoveryManager.getInstance().getEurekaClientConfig();
        String replacement;
        if (clientConfig == null) {
            replacement = "__";
        } else {
            replacement = clientConfig.getEscapeCharReplacement();
        }
        StringBuilder sb = new StringBuilder(keyTemplate.length() + 1);
        for (char c : keyTemplate.toCharArray()) {
            if (c == '_') {
                sb.append(replacement);
            } else {
                sb.append(c);
            }
        }
        return sb.toString();
    }

    public  T readValue(Class type, InputStream entityStream) throws IOException {
        ObjectReader reader = objectReaderByClass.get(type);
        if (reader == null) {
            return mapper.readValue(entityStream, type);
        }
        return reader.readValue(entityStream);
    }

    public  T readValue(Class type, String text) throws IOException {
        ObjectReader reader = objectReaderByClass.get(type);
        if (reader == null) {
            return mapper.readValue(text, type);
        }
        return reader.readValue(text);
    }

    public  void writeTo(T object, OutputStream entityStream) throws IOException {
        ObjectWriter writer = objectWriterByClass.get(object.getClass());
        if (writer == null) {
            mapper.writeValue(entityStream, object);
        } else {
            writer.writeValue(entityStream, object);
        }
    }

    public  String writeToString(T object) {
        try {
            ObjectWriter writer = objectWriterByClass.get(object.getClass());
            if (writer == null) {
                return mapper.writeValueAsString(object);
            }
            return writer.writeValueAsString(object);
        } catch (IOException e) {
            throw new IllegalArgumentException("Cannot encode provided object", e);
        }
    }

    public static EurekaJacksonCodec getInstance() {
        return INSTANCE;
    }

    public static void setInstance(EurekaJacksonCodec instance) {
        INSTANCE = instance;
    }

    public static class DataCenterInfoSerializer extends JsonSerializer {
        @Override
        public void serializeWithType(DataCenterInfo dataCenterInfo, JsonGenerator jgen,
                                      SerializerProvider provider, TypeSerializer typeSer)
                throws IOException, JsonProcessingException {
            jgen.writeStartObject();

            // XStream encoded adds this for backwards compatibility issue. Not sure if needed anymore
            if (dataCenterInfo.getName() == Name.Amazon) {
                jgen.writeStringField("@class", "com.netflix.appinfo.AmazonInfo");
            } else {
                jgen.writeStringField("@class", "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo");
            }

            jgen.writeStringField(ELEM_NAME, dataCenterInfo.getName().name());

            if (dataCenterInfo.getName() == Name.Amazon) {
                AmazonInfo aInfo = (AmazonInfo) dataCenterInfo;
                jgen.writeObjectField(DATACENTER_METADATA, aInfo.getMetadata());
            }
            jgen.writeEndObject();
        }

        @Override
        public void serialize(DataCenterInfo dataCenterInfo, JsonGenerator jgen, SerializerProvider provider) throws IOException {
            serializeWithType(dataCenterInfo, jgen, provider, null);
        }
    }

    public static class DataCenterInfoDeserializer extends JsonDeserializer {

        @Override
        public DataCenterInfo deserialize(JsonParser jp, DeserializationContext context) throws IOException {
            JsonNode node = jp.getCodec().readTree(jp);
            final Name name = Name.valueOf(node.get(ELEM_NAME).asText());
            if (name != Name.Amazon) {
                return new DataCenterInfo() {
                    @Override
                    public Name getName() {
                        return name;
                    }
                };
            }

            Map metaData = new HashMap();
            JsonNode metaNode = node.get(DATACENTER_METADATA);
            Iterator metaNamesIt = metaNode.fieldNames();
            while (metaNamesIt.hasNext()) {
                String key = metaNamesIt.next();
                String value = metaNode.get(key).asText();
                metaData.put(StringCache.intern(key), StringCache.intern(value));
            }

            AmazonInfo amazonInfo = new AmazonInfo();
            amazonInfo.setMetadata(metaData);

            return amazonInfo;
        }
    }

    public static class LeaseInfoDeserializer extends JsonDeserializer {

        protected static final String ELEM_RENEW_INT = "renewalIntervalInSecs";
        protected static final String ELEM_DURATION = "durationInSecs";
        protected static final String ELEM_REG_TIMESTAMP = "registrationTimestamp";
        protected static final String ELEM_LAST_RENEW_TIMESTAMP = "lastRenewalTimestamp";
        protected static final String ELEM_EVICTION_TIMESTAMP = "evictionTimestamp";
        protected static final String ELEM_SERVICE_UP_TIMESTAMP = "serviceUpTimestamp";

        @Override
        public LeaseInfo deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
            LeaseInfo.Builder builder = LeaseInfo.Builder.newBuilder();

            JsonNode node = jp.getCodec().readTree(jp);
            Iterator fieldNames = node.fieldNames();
            while (fieldNames.hasNext()) {
                String nodeName = fieldNames.next();
                if (!node.get(nodeName).isNull()) {
                    long longValue = node.get(nodeName).asLong();
                    if (ELEM_DURATION.equals(nodeName)) {
                        builder.setDurationInSecs((int) longValue);
                    } else if (ELEM_EVICTION_TIMESTAMP.equals(nodeName)) {
                        builder.setEvictionTimestamp(longValue);
                    } else if (ELEM_LAST_RENEW_TIMESTAMP.equals(nodeName)) {
                        builder.setRenewalTimestamp(longValue);
                    } else if (ELEM_REG_TIMESTAMP.equals(nodeName)) {
                        builder.setRegistrationTimestamp(longValue);
                    } else if (ELEM_RENEW_INT.equals(nodeName)) {
                        builder.setRenewalIntervalInSecs((int) longValue);
                    } else if (ELEM_SERVICE_UP_TIMESTAMP.equals(nodeName)) {
                        builder.setServiceUpTimestamp(longValue);
                    }
                }
            }
            return builder.build();
        }
    }

    public static class InstanceInfoSerializer extends JsonSerializer {
        // For backwards compatibility
        public static final String METADATA_COMPATIBILITY_KEY = "@class";
        public static final String METADATA_COMPATIBILITY_VALUE = "java.util.Collections$EmptyMap";
        protected static final Object EMPTY_METADATA = Collections.singletonMap(METADATA_COMPATIBILITY_KEY, METADATA_COMPATIBILITY_VALUE);

        @Override
        public void serialize(InstanceInfo info, JsonGenerator jgen, SerializerProvider provider) throws IOException {
            jgen.writeStartObject();

            if (info.getInstanceId() != null) {
                jgen.writeStringField(ELEM_INSTANCE_ID, info.getInstanceId());
            }
            jgen.writeStringField(ELEM_HOST, info.getHostName());
            jgen.writeStringField(ELEM_APP, info.getAppName());
            jgen.writeStringField(ELEM_IP, info.getIPAddr());

            if (!("unknown".equals(info.getSID()) || "na".equals(info.getSID()))) {
                jgen.writeStringField(ELEM_SID, info.getSID());
            }

            jgen.writeStringField(ELEM_STATUS, info.getStatus().name());
            jgen.writeStringField(ELEM_OVERRIDDEN_STATUS, info.getOverriddenStatus().name());

            jgen.writeFieldName(ELEM_PORT);
            jgen.writeStartObject();
            jgen.writeNumberField("$", info.getPort());
            jgen.writeStringField("@enabled", Boolean.toString(info.isPortEnabled(PortType.UNSECURE)));
            jgen.writeEndObject();

            jgen.writeFieldName(ELEM_SECURE_PORT);
            jgen.writeStartObject();
            jgen.writeNumberField("$", info.getSecurePort());
            jgen.writeStringField("@enabled", Boolean.toString(info.isPortEnabled(PortType.SECURE)));
            jgen.writeEndObject();

            jgen.writeNumberField(ELEM_COUNTRY_ID, info.getCountryId());

            if (info.getDataCenterInfo() != null) {
                jgen.writeObjectField(NODE_DATACENTER, info.getDataCenterInfo());
            }
            if (info.getLeaseInfo() != null) {
                jgen.writeObjectField(NODE_LEASE, info.getLeaseInfo());
            }

            Map metadata = info.getMetadata();
            if (metadata != null) {
                if (metadata.isEmpty()) {
                    jgen.writeObjectField(NODE_METADATA, EMPTY_METADATA);
                } else {
                    jgen.writeObjectField(NODE_METADATA, metadata);
                }
            }
            autoMarshalEligible(info, jgen);

            jgen.writeEndObject();
        }

        protected void autoMarshalEligible(Object o, JsonGenerator jgen) {
            try {
                Class c = o.getClass();
                Field[] fields = c.getDeclaredFields();
                Annotation annotation;
                for (Field f : fields) {
                    annotation = f.getAnnotation(Auto.class);
                    if (annotation != null) {
                        f.setAccessible(true);
                        if (f.get(o) != null) {
                            jgen.writeStringField(f.getName(), String.valueOf(f.get(o)));
                        }

                    }
                }
            } catch (Throwable th) {
                logger.error("Error in marshalling the object", th);
            }
        }
    }

    public static class InstanceInfoDeserializer extends JsonDeserializer {
        protected ObjectMapper mapper;

        protected InstanceInfoDeserializer(ObjectMapper mapper) {
            this.mapper = mapper;
        }

        @Override
        public InstanceInfo deserialize(JsonParser jp, DeserializationContext context) throws IOException {
            InstanceInfo.Builder builder = InstanceInfo.Builder.newBuilder();

            JsonNode node = jp.getCodec().readTree(jp);

            /**
             * These are set via single call to
             * {@link com.netflix.appinfo.InstanceInfo.Builder#setHealthCheckUrlsForDeser(String, String, String)}.
             */
            String healthChecUrl = null;
            String healthCheckSecureUrl = null;

            Iterator fieldNames = node.fieldNames();
            while (fieldNames.hasNext()) {
                String fieldName = fieldNames.next();
                JsonNode fieldNode = node.get(fieldName);

                if (!fieldNode.isNull()) {
                    if (ELEM_HOST.equals(fieldName)) {
                        builder.setHostName(fieldNode.asText());
                    } else if (ELEM_INSTANCE_ID.equals(fieldName)) {
                        builder.setInstanceId(fieldNode.asText());
                    } else if (ELEM_APP.equals(fieldName)) {
                        builder.setAppName(fieldNode.asText());
                    } else if (ELEM_IP.equals(fieldName)) {
                        builder.setIPAddr(fieldNode.asText());
                    } else if (ELEM_SID.equals(fieldName)) {
                        builder.setSID(fieldNode.asText());
                    } else if (ELEM_IDENTIFYING_ATTR.equals(fieldName)) {
                        // nothing;
                    } else if (ELEM_STATUS.equals(fieldName)) {
                        builder.setStatus(InstanceStatus.toEnum(fieldNode.asText()));
                    } else if (ELEM_OVERRIDDEN_STATUS.equals(fieldName)) {
                        builder.setOverriddenStatus(InstanceStatus.toEnum(fieldNode.asText()));
                    } else if (ELEM_PORT.equals(fieldName)) {
                        int port = fieldNode.get("$").asInt();
                        boolean enabled = fieldNode.get("@enabled").asBoolean();
                        builder.setPort(port);
                        builder.enablePort(PortType.UNSECURE, enabled);
                    } else if (ELEM_SECURE_PORT.equals(fieldName)) {
                        int port = fieldNode.get("$").asInt();
                        boolean enabled = fieldNode.get("@enabled").asBoolean();
                        builder.setSecurePort(port);
                        builder.enablePort(PortType.SECURE, enabled);
                    } else if (ELEM_COUNTRY_ID.equals(fieldName)) {
                        builder.setCountryId(Integer.valueOf(fieldNode.asText()).intValue());
                    } else if (NODE_DATACENTER.equals(fieldName)) {
                        builder.setDataCenterInfo(mapper.treeToValue(fieldNode, DataCenterInfo.class));
                    } else if (NODE_LEASE.equals(fieldName)) {
                        builder.setLeaseInfo(mapper.treeToValue(fieldNode, LeaseInfo.class));
                    } else if (NODE_METADATA.equals(fieldName)) {
                        Map meta = null;
                        Iterator metaNameIt = fieldNode.fieldNames();
                        while (metaNameIt.hasNext()) {
                            String key = StringCache.intern(metaNameIt.next());
                            if (key.equals("@class")) { // For backwards compatibility
                                if (meta == null && !metaNameIt.hasNext()) { // Optimize for empty maps
                                    meta = Collections.emptyMap();
                                }
                            } else {
                                if (meta == null) {
                                    meta = new ConcurrentHashMap();
                                }
                                String value = StringCache.intern(fieldNode.get(key).asText());
                                meta.put(key, value);
                            }
                        }
                        if (meta == null) {
                            meta = Collections.emptyMap();
                        }
                        builder.setMetadata(meta);
                    } else if (ELEM_HEALTHCHECKURL.equals(fieldName)) {
                        healthChecUrl = fieldNode.asText();
                    } else if (ELEM_SECHEALTHCHECKURL.equals(fieldName)) {
                        healthCheckSecureUrl = fieldNode.asText();
                    } else if (ELEM_APPGROUPNAME.equals(fieldName)) {
                        builder.setAppGroupName(fieldNode.asText());
                    } else if (ELEM_HOMEPAGEURL.equals(fieldName)) {
                        builder.setHomePageUrlForDeser(fieldNode.asText());
                    } else if (ELEM_STATUSPAGEURL.equals(fieldName)) {
                        builder.setStatusPageUrlForDeser(fieldNode.asText());
                    } else if (ELEM_VIPADDRESS.equals(fieldName)) {
                        builder.setVIPAddressDeser(fieldNode.asText());
                    } else if (ELEM_SECVIPADDRESS.equals(fieldName)) {
                        builder.setSecureVIPAddressDeser(fieldNode.asText());
                    } else if (ELEM_ISCOORDINATINGDISCSOERVER.equals(fieldName)) {
                        builder.setIsCoordinatingDiscoveryServer(fieldNode.asBoolean());
                    } else if (ELEM_LASTUPDATEDTS.equals(fieldName)) {
                        builder.setLastUpdatedTimestamp(fieldNode.asLong());
                    } else if (ELEM_LASTDIRTYTS.equals(fieldName)) {
                        builder.setLastDirtyTimestamp(fieldNode.asLong());
                    } else if (ELEM_ACTIONTYPE.equals(fieldName)) {
                        builder.setActionType(ActionType.valueOf(fieldNode.asText()));
                    } else if (ELEM_ASGNAME.equals(fieldName)) {
                        builder.setASGName(fieldNode.asText());
                    } else {
                        autoUnmarshalEligible(fieldName, fieldNode.asText(), builder.getRawInstance());
                    }
                }
            }
            builder.setHealthCheckUrlsForDeser(healthChecUrl, healthCheckSecureUrl);

            return builder.build();
        }

        protected void autoUnmarshalEligible(String fieldName, String value, Object o) {
            try {
                Class c = o.getClass();
                Field f = null;
                try {
                    f = c.getDeclaredField(fieldName);
                } catch (NoSuchFieldException e) {
                    // TODO XStream version increments metrics counter here
                }
                if (f == null) {
                    return;
                }
                Annotation annotation = f.getAnnotation(Auto.class);
                if (annotation == null) {
                    return;
                }
                f.setAccessible(true);

                Class returnClass = f.getType();
                if (value != null) {
                    if (!String.class.equals(returnClass)) {
                        Method method = returnClass.getDeclaredMethod("valueOf", java.lang.String.class);
                        Object valueObject = method.invoke(returnClass, value);
                        f.set(o, valueObject);
                    } else {
                        f.set(o, value);

                    }
                }
            } catch (Throwable th) {
                logger.error("Error in unmarshalling the object:", th);
            }
        }

    }

    public static class ApplicationSerializer extends JsonSerializer {
        @Override
        public void serialize(Application value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
            jgen.writeStartObject();
            jgen.writeStringField(ELEM_NAME, value.getName());
            jgen.writeObjectField(ELEM_INSTANCE, value.getInstances());
            jgen.writeEndObject();
        }
    }

    public static class ApplicationDeserializer extends JsonDeserializer {

        protected ObjectMapper mapper;

        public ApplicationDeserializer(ObjectMapper mapper) {
            this.mapper = mapper;
        }

        @Override
        public Application deserialize(JsonParser jp, DeserializationContext context) throws IOException {
            JsonNode node = jp.getCodec().readTree(jp);

            Application application = new Application(node.get(ELEM_NAME).asText());

            JsonNode instanceNode = node.get(ELEM_INSTANCE);
            if (instanceNode != null) {
                if (instanceNode instanceof ArrayNode) {
                    ArrayNode instancesNode = (ArrayNode) instanceNode;
                    if (instancesNode != null) {
                        for (JsonNode nextNode : instancesNode) {
                            application.addInstance(mapper.treeToValue(nextNode, InstanceInfo.class));
                        }
                    }
                } else {
                    application.addInstance(mapper.treeToValue(instanceNode, InstanceInfo.class));
                }
            }
            return application;
        }
    }

    public static class ApplicationsSerializer extends JsonSerializer {
        protected String versionDeltaKey;
        protected String appHashCodeKey;

        public ApplicationsSerializer(String versionDeltaKey, String appHashCodeKey) {
            this.versionDeltaKey = versionDeltaKey;
            this.appHashCodeKey = appHashCodeKey;
        }

        @Override
        public void serialize(Applications applications, JsonGenerator jgen, SerializerProvider provider) throws IOException {
            jgen.writeStartObject();
            jgen.writeStringField(versionDeltaKey, applications.getVersion().toString());
            jgen.writeStringField(appHashCodeKey, applications.getAppsHashCode());
            jgen.writeObjectField(NODE_APP, applications.getRegisteredApplications());
        }
    }

    public static class ApplicationsDeserializer extends JsonDeserializer {
        protected ObjectMapper mapper;
        protected String versionDeltaKey;
        protected String appHashCodeKey;

        public ApplicationsDeserializer(ObjectMapper mapper, String versionDeltaKey, String appHashCodeKey) {
            this.mapper = mapper;
            this.versionDeltaKey = versionDeltaKey;
            this.appHashCodeKey = appHashCodeKey;
        }

        @Override
        public Applications deserialize(JsonParser jp, DeserializationContext context) throws IOException {
            Applications apps = new Applications();

            JsonNode node = jp.getCodec().readTree(jp);

            if (node.get(versionDeltaKey) != null) {
                apps.setVersion(node.get(versionDeltaKey).asLong());
            }
            if (node.get(appHashCodeKey) != null) {
                apps.setAppsHashCode(node.get(appHashCodeKey).asText());
            }
            JsonNode appNode = node.get(NODE_APP);
            if (appNode != null) {
                if (appNode instanceof ArrayNode) {
                    ArrayNode appsNode = (ArrayNode) appNode;
                    for (JsonNode item : appsNode) {
                        apps.addApplication(mapper.treeToValue(item, Application.class));
                    }
                } else {
                    apps.addApplication(mapper.treeToValue(appNode, Application.class));
                }
            }
            return apps;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy