/**
* 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<Object> {
@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<Class<? extends Annotation>, 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<String> 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;
}
}