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

org.datacleaner.descriptors.JsonSchemaConfiguredPropertyDescriptorImpl Maven / Gradle / Ivy

/**
 * DataCleaner (community edition)
 * Copyright (C) 2014 Neopost - Customer Information Management
 *
 * This copyrighted material is made available to anyone wishing to use, modify,
 * copy, or redistribute it subject to the terms and conditions of the GNU
 * Lesser General Public License, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this distribution; if not, write to:
 * Free Software Foundation, Inc.
 * 51 Franklin Street, Fifth Floor
 * Boston, MA  02110-1301  USA
 */
package org.datacleaner.descriptors;

import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;

import org.datacleaner.api.Converter;
import org.datacleaner.restclient.Serializator;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.module.jsonSchema.JsonSchema;
import com.fasterxml.jackson.module.jsonSchema.types.ArraySchema;
import com.fasterxml.jackson.module.jsonSchema.types.BooleanSchema;
import com.fasterxml.jackson.module.jsonSchema.types.IntegerSchema;
import com.fasterxml.jackson.module.jsonSchema.types.NumberSchema;
import com.fasterxml.jackson.module.jsonSchema.types.StringSchema;
import com.fasterxml.jackson.module.jsonSchema.types.ValueTypeSchema;

/**
 * Property descriptor for properties of RemoteTransformer, which has no
 * appropriate class on the client side. (Class is available only on server, but
 * is not part of standard DataCleaner installation). The data type of the
 * property is represented by Json Schema. Special care was taken to support
 * enumerations.
 *
 * @Since 9/1/15
 */
public class JsonSchemaConfiguredPropertyDescriptorImpl extends RemoteConfiguredPropertyDescriptor
        implements EnumerationProvider {

    private class EnumerationValueConverter implements Converter {

        @Override
        public Object fromString(final Class type, final String serializedForm) {
            for (final EnumerationValue valueCandidate : enumValues) {
                if (valueCandidate.getValue().equals(serializedForm)) {
                    return valueCandidate;
                } else if (valueCandidate.getName().equals(serializedForm)) {
                    return valueCandidate;
                } else {
                    for (final String alias : valueCandidate.getAliases()) {
                        if (alias.equals(serializedForm)) {
                            return valueCandidate;
                        }
                    }
                }
            }
            return null;
        }

        @Override
        public String toString(final Object instance) {
            if (instance == null) {
                return null;
            }
            if (instance instanceof EnumerationValue) {
                return ((EnumerationValue) instance).getValue();
            }
            if (instance instanceof Enum) {
                return ((Enum) instance).name();
            }
            throw new IllegalArgumentException("Cannot serialize value of type " + instance.getClass());
        }

        @Override
        public boolean isConvertable(final Class type) {
            return type.isAssignableFrom(EnumerationValue.class) && isEnum();
        }
    }

    private static final long serialVersionUID = 1L;
    private final JsonSchema schema;
    private final boolean isInputColumn;
    private boolean isArray;
    private Class baseType;
    private EnumerationValue[] enumValues;

    public JsonSchemaConfiguredPropertyDescriptorImpl(final String name, final JsonSchema schema,
            final boolean isInputColumn, final String description, final boolean required,
            final ComponentDescriptor component, final Map, Annotation> annotations,
            final JsonNode defaultValue) {
        super(name, description, required, component, annotations, defaultValue);
        this.schema = schema;
        this.isInputColumn = isInputColumn;
        init();
    }

    private void init() {
        isArray = schema.isArraySchema();
        final JsonSchema baseSchema;

        if (isArray) {
            baseSchema = ((ArraySchema) schema).getItems().asSingleItems().getSchema();
        } else {
            baseSchema = schema;
        }

        enumValues = new EnumerationValue[0]; // default
        if (baseSchema instanceof ValueTypeSchema) {
            final Set enums = ((ValueTypeSchema) baseSchema).getEnums();
            if (enums != null && !enums.isEmpty()) {
                enumValues = new EnumerationValue[enums.size()];
                int i = 0;
                for (final String value : enums) {

                    final String enumValue;
                    final String enumName;
                    final String[] enumAliases;
                    final String[] tokens = value.split(Serializator.ENUM_ALIAS_SEPARATOR);
                    if (tokens.length == 0) {
                        continue;
                    }
                    if (tokens.length == 1) {
                        enumValue = tokens[0];
                        enumName = tokens[0];
                        enumAliases = new String[0];
                    } else {
                        enumValue = tokens[0];
                        enumName = tokens[1];
                        enumAliases = Arrays.copyOfRange(tokens, 2, tokens.length);
                    }
                    enumValues[i++] = new EnumerationValue(enumValue, enumName, enumAliases);
                }
                Arrays.sort(enumValues);
            }
        }

        // must be called after enums are initialized
        baseType = schemaToJavaType(baseSchema);
    }

    @Override
    public Class getType() {
        if (isArray()) {
            return Array.newInstance(getBaseType(), 0).getClass();
        }
        return baseType;
    }

    @Override
    public boolean isArray() {
        return isArray;
    }

    @Override
    public Class getBaseType() {
        return baseType;
    }

    @Override
    public boolean isInputColumn() {
        return isInputColumn;
    }

    private Class schemaToJavaType(final JsonSchema schema) {
        // try to convert
        if (isEnum()) {
            return EnumerationValue.class;
        }
        if (schema instanceof StringSchema) {
            return String.class;
        }
        if (schema instanceof IntegerSchema) {
            return Integer.class;
        }
        if (schema instanceof BooleanSchema) {
            return Boolean.class;
        }
        if (schema instanceof NumberSchema) {
            return Double.class;
        }
        // fallback
        return JsonNode.class;
    }

    public boolean isEnum() {
        return enumValues != null && enumValues.length > 0;
    }

    @Override
    public EnumerationValue[] values() {
        return enumValues;
    }

    @Override
    public EnumerationValue forString(final String value) {
        if (enumValues == null) {
            return null;
        }
        for (final EnumerationValue candidate : enumValues) {
            if (value.equals(candidate.getValue()) || value.equals(candidate.getName())) {
                return candidate;
            }
            for (final String alias : candidate.getAliases()) {
                if (value.equals(alias)) {
                    return candidate;
                }
            }
        }
        return null;
    }

    @Override
    public Converter createCustomConverter() {
        return isEnum() ? new EnumerationValueConverter() : null;
    }
}