ConfigurationSettingJsonSerializer.java

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.data.appconfiguration.implementation;

import com.azure.core.util.CoreUtils;
import com.azure.data.appconfiguration.models.ConfigurationSetting;
import com.azure.data.appconfiguration.models.FeatureFlagConfigurationSetting;
import com.azure.data.appconfiguration.models.FeatureFlagFilter;
import com.azure.data.appconfiguration.models.SecretReferenceConfigurationSetting;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;

import java.io.IOException;
import java.io.StringWriter;
import java.time.format.DateTimeFormatter;
import java.util.Map;

/**
 * Custom JSON serializer for {@link ConfigurationSetting} and its derived classes,
 * {@link SecretReferenceConfigurationSetting} and {@link FeatureFlagConfigurationSetting}.
 */
public final class ConfigurationSettingJsonSerializer extends JsonSerializer<ConfigurationSetting>  {

    static final String ID = "id";
    static final String DESCRIPTION = "description";
    static final String DISPLAY_NAME = "display_name";
    static final String ENABLED = "enabled";
    static final String CONDITIONS = "conditions";
    static final String CLIENT_FILTERS = "client_filters";
    static final String NAME = "name";
    static final String PARAMETERS = "parameters";

    static final String KEY = "key";
    static final String LABEL = "label";
    static final String VALUE = "value";
    static final String CONTENT_TYPE = "content_type";
    static final String ETAG = "etag";
    static final String LAST_MODIFIED = "last_modified";
    static final String LOCKED = "locked";
    static final String TAGS = "tags";
    static final String URI = "uri";

    static final SimpleModule MODULE;

    static {
        MODULE = new SimpleModule();
        MODULE.addSerializer(ConfigurationSetting.class, new ConfigurationSettingJsonSerializer());
    }

    /**
     * Gets a module wrapping this serializer as an adapter for the Jackson
     * ObjectMapper.
     *
     * @return a simple module to be plugged onto Jackson ObjectMapper.
     */
    public static SimpleModule getModule() {
        return MODULE;
    }

    @Override
    public void serialize(ConfigurationSetting value, JsonGenerator gen, SerializerProvider serializers)
        throws IOException {
        write(value, gen);
    }

    private static void write(ConfigurationSetting value, JsonGenerator gen) throws IOException {
        gen.writeStartObject();
        String settingValue;
        if (value instanceof FeatureFlagConfigurationSetting) {
            settingValue = writeFeatureFlagConfigurationSetting((FeatureFlagConfigurationSetting) value);
        } else if (value instanceof SecretReferenceConfigurationSetting) {
            settingValue = writeSecretReferenceConfigurationSetting((SecretReferenceConfigurationSetting) value);
        } else {
            settingValue = value.getValue();
        }
        gen.writeStringField(VALUE, settingValue);

        gen.writeStringField(KEY, value.getKey());

        if (!CoreUtils.isNullOrEmpty(value.getLabel())) {
            gen.writeStringField(LABEL, value.getLabel());
        }

        gen.writeStringField(CONTENT_TYPE, value.getContentType());
        gen.writeStringField(ETAG, value.getETag());
        gen.writeBooleanField(LOCKED, value.isReadOnly());

        gen.writeObjectFieldStart(TAGS);
        writeMapProperties(value.getTags(), gen);
        gen.writeEndObject();

        if (value.getLastModified() != null) {
            gen.writeStringField(LAST_MODIFIED, value.getLastModified().format(DateTimeFormatter.ISO_DATE_TIME));
        }
        gen.writeEndObject();
    }

    /**
     * Serialize the strongly-type property, {@code secretId} of {@link SecretReferenceConfigurationSetting} into a
     * JSON format string, which is the {@code value} of {@link SecretReferenceConfigurationSetting}.
     *
     * @param setting the {@link SecretReferenceConfigurationSetting}.
     * @return a JSON format string that represents the {@code value} of {@link SecretReferenceConfigurationSetting}.
     * @throws IOException if {@link JsonGenerator} can not be created.
     */
    public static String writeSecretReferenceConfigurationSetting(SecretReferenceConfigurationSetting setting)
        throws IOException {
        // The setting's value is expected to be a JSON string for the SecretReferenceConfigurationSetting,
        // so it is better to use another JSON generator to constructor the value as JSON string, flush into the
        // StringWriter.
        final StringWriter jsonObjectWriter = new StringWriter();
        final JsonGenerator gen = new JsonFactory().createGenerator(jsonObjectWriter);
        gen.writeStartObject();
        gen.writeStringField(URI, setting.getSecretId());
        gen.writeEndObject();
        gen.close();
        return jsonObjectWriter.toString();
    }

    /**
     * Serialize the strong-type properties, such as {@code featureId} of {@link FeatureFlagConfigurationSetting}
     * into a JSON format string, which is the {@code value} of {@link FeatureFlagConfigurationSetting}.
     *
     * @param setting the {@link FeatureFlagConfigurationSetting}.
     * @return a JSON format string that represents the {@code value} of {@link FeatureFlagConfigurationSetting}.
     * @throws IOException if {@link JsonGenerator} can not be created.
     */
    public static String writeFeatureFlagConfigurationSetting(FeatureFlagConfigurationSetting setting)
        throws IOException {
        // The setting's value is expected to be a JSON string for the FeatureFlagConfigurationSetting,
        // so it is better to use another JSON generator to constructor the value as JSON string, flush into the
        // StringWriter.
        final StringWriter jsonObjectWriter = new StringWriter();
        final JsonGenerator gen = new JsonFactory().createGenerator(jsonObjectWriter);
        gen.writeStartObject();

        gen.writeStringField(ID, setting.getFeatureId());
        gen.writeStringField(DESCRIPTION, setting.getDescription());
        gen.writeStringField(DISPLAY_NAME, setting.getDisplayName());
        gen.writeBooleanField(ENABLED, setting.isEnabled());

        gen.writeObjectFieldStart(CONDITIONS);
        gen.writeArrayFieldStart(CLIENT_FILTERS);
        for (FeatureFlagFilter filter : setting.getClientFilters()) {
            gen.writeStartObject();
            gen.writeStringField(NAME, filter.getName());
            gen.writeObjectFieldStart(PARAMETERS);
            writeMapProperties(filter.getParameters(), gen);
            gen.writeEndObject(); // parameters object
            gen.writeEndObject(); // each filter object
        }

        gen.writeEndArray();
        gen.writeEndObject();

        gen.writeEndObject();
        gen.close();

        return jsonObjectWriter.toString();
    }

    private static void writeMapProperties(Map<String, ? extends Object> properties, JsonGenerator gen)
        throws IOException {
        if (CoreUtils.isNullOrEmpty(properties)) {
            return;
        }

        for (Map.Entry<String, ? extends Object> property : properties.entrySet()) {
            gen.writeFieldName(property.getKey());
            gen.writeObject(property.getValue().toString());
        }
    }
}