org.talend.components.netsuite.NetSuiteDatasetRuntimeImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.talend.components.netsuite.NetSuiteDatasetRuntimeImpl.java

Source

// ============================================================================
//
// Copyright (C) 2006-2017 Talend Inc. - www.talend.com
//
// This source code is available under agreement available at
// %InstallDIR%\features\org.talend.rcp.branding.%PRODUCTNAME%\%PRODUCTNAME%license.txt
//
// You should have received a copy of the agreement
// along with this program; if not, write to Talend SA
// 9 rue Pages 92150 Suresnes, France
//
// ============================================================================

package org.talend.components.netsuite;

import static org.talend.components.netsuite.client.model.beans.Beans.toInitialLower;
import static org.talend.components.netsuite.client.model.beans.Beans.toInitialUpper;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.datatype.XMLGregorianCalendar;

import org.apache.avro.JsonProperties;
import org.apache.avro.Schema;
import org.apache.commons.lang3.StringUtils;
import org.talend.components.api.exception.ComponentException;
import org.talend.components.netsuite.client.MetaDataSource;
import org.talend.components.netsuite.client.NetSuiteException;
import org.talend.components.netsuite.client.NsRef;
import org.talend.components.netsuite.client.model.BasicMetaData;
import org.talend.components.netsuite.client.model.CustomFieldDesc;
import org.talend.components.netsuite.client.model.CustomRecordTypeInfo;
import org.talend.components.netsuite.client.model.FieldDesc;
import org.talend.components.netsuite.client.model.RecordTypeDesc;
import org.talend.components.netsuite.client.model.RecordTypeInfo;
import org.talend.components.netsuite.client.model.RefType;
import org.talend.components.netsuite.client.model.SearchRecordTypeDesc;
import org.talend.components.netsuite.client.model.TypeDesc;
import org.talend.components.netsuite.client.model.customfield.CustomFieldRefType;
import org.talend.components.netsuite.client.model.search.SearchFieldOperatorName;
import org.talend.components.netsuite.schema.SearchFieldInfo;
import org.talend.components.netsuite.schema.SearchInfo;
import org.talend.daikon.NamedThing;
import org.talend.daikon.SimpleNamedThing;
import org.talend.daikon.avro.AvroUtils;
import org.talend.daikon.avro.SchemaConstants;

/**
 * Provides information about NetSuite data set for components in design time or run time.
 */
public class NetSuiteDatasetRuntimeImpl implements NetSuiteDatasetRuntime {

    /** Source of meta data. */
    private MetaDataSource metaDataSource;

    /**
     * Create new instance using given {@code MetaDataSource}.
     *
     * @param metaDataSource source of meta data to be used
     */
    public NetSuiteDatasetRuntimeImpl(MetaDataSource metaDataSource) {
        this.metaDataSource = metaDataSource;
    }

    @Override
    public List<NamedThing> getRecordTypes() {
        try {
            Collection<RecordTypeInfo> recordTypeList = metaDataSource.getRecordTypes();

            List<NamedThing> recordTypes = new ArrayList<>(recordTypeList.size());
            for (RecordTypeInfo recordTypeInfo : recordTypeList) {
                recordTypes.add(new SimpleNamedThing(recordTypeInfo.getName(), recordTypeInfo.getDisplayName()));
            }

            // Sort by display name in alphabetical order
            Collections.sort(recordTypes, new Comparator<NamedThing>() {

                @Override
                public int compare(NamedThing o1, NamedThing o2) {
                    return o1.getDisplayName().compareTo(o2.getDisplayName());
                }
            });
            return recordTypes;
        } catch (NetSuiteException e) {
            throw new ComponentException(e);
        }
    }

    @Override
    public Schema getSchema(String typeName) {
        try {
            final RecordTypeInfo recordTypeInfo = metaDataSource.getRecordType(typeName);
            final TypeDesc typeDesc = metaDataSource.getTypeInfo(typeName);

            List<FieldDesc> fieldDescList = new ArrayList<>(typeDesc.getFields());
            // Sort in alphabetical order
            Collections.sort(fieldDescList, FieldDescComparator.INSTANCE);

            Schema schema = inferSchemaForType(typeDesc.getTypeName(), fieldDescList);
            augmentSchemaWithCustomMetaData(metaDataSource, schema, recordTypeInfo, fieldDescList);

            return schema;
        } catch (NetSuiteException e) {
            throw new ComponentException(e);
        }
    }

    @Override
    public List<NamedThing> getSearchableTypes() {
        try {
            List<NamedThing> searchableTypes = new ArrayList<>(metaDataSource.getSearchableTypes());

            // Sort by display name in alphabetical order
            Collections.sort(searchableTypes, new Comparator<NamedThing>() {

                @Override
                public int compare(NamedThing o1, NamedThing o2) {
                    return o1.getDisplayName().compareTo(o2.getDisplayName());
                }
            });

            return searchableTypes;
        } catch (NetSuiteException e) {
            throw new ComponentException(e);
        }
    }

    @Override
    public SearchInfo getSearchInfo(String typeName) {
        try {
            final SearchRecordTypeDesc searchInfo = metaDataSource.getSearchRecordType(typeName);
            final TypeDesc searchRecordInfo = metaDataSource.getBasicMetaData()
                    .getTypeInfo(searchInfo.getSearchBasicClass());

            List<FieldDesc> fieldDescList = searchRecordInfo.getFields();

            List<SearchFieldInfo> fields = new ArrayList<>(fieldDescList.size());
            for (FieldDesc fieldDesc : fieldDescList) {
                SearchFieldInfo field = new SearchFieldInfo(fieldDesc.getName(), fieldDesc.getValueType());
                fields.add(field);
            }

            // Sort by display name in alphabetical order
            Collections.sort(fields, new Comparator<SearchFieldInfo>() {

                @Override
                public int compare(SearchFieldInfo o1, SearchFieldInfo o2) {
                    return o1.getName().compareTo(o2.getName());
                }
            });

            return new SearchInfo(searchRecordInfo.getTypeName(), fields);

        } catch (NetSuiteException e) {
            throw new ComponentException(e);
        }
    }

    @Override
    public Schema getSchemaForUpdate(String typeName) {
        try {
            final RecordTypeInfo recordTypeInfo = metaDataSource.getRecordType(typeName);
            final TypeDesc typeDesc = metaDataSource.getTypeInfo(typeName);

            List<FieldDesc> fieldDescList = new ArrayList<>(typeDesc.getFields());
            // Sort in alphabetical order
            Collections.sort(fieldDescList, FieldDescComparator.INSTANCE);

            Schema schema = inferSchemaForType(typeDesc.getTypeName(), fieldDescList);
            augmentSchemaWithCustomMetaData(metaDataSource, schema, recordTypeInfo, typeDesc.getFields());

            return schema;
        } catch (NetSuiteException e) {
            throw new ComponentException(e);
        }
    }

    @Override
    public Schema getSchemaForDelete(String typeName) {
        return getSchemaForRecordRef(typeName);
    }

    public Schema getSchemaForRecordRef(String typeName) {
        try {
            // Get info for target record type
            final RecordTypeInfo referencedRecordTypeInfo = metaDataSource.getRecordType(typeName);
            final RefType refType = referencedRecordTypeInfo.getRefType();
            // Get type info for record ref
            final TypeDesc typeDesc = metaDataSource.getTypeInfo(refType.getTypeName());

            List<FieldDesc> fieldDescList = new ArrayList<>(typeDesc.getFields());
            // Sort in alphabetical order
            Collections.sort(fieldDescList, FieldDescComparator.INSTANCE);

            Schema schema = inferSchemaForType(typeDesc.getTypeName(), fieldDescList);
            augmentSchemaWithCustomMetaData(metaDataSource, schema, referencedRecordTypeInfo, null);

            return schema;
        } catch (NetSuiteException e) {
            throw new ComponentException(e);
        }
    }

    @Override
    public List<String> getSearchFieldOperators() {
        List<SearchFieldOperatorName> operatorList = new ArrayList<>(
                metaDataSource.getBasicMetaData().getSearchOperatorNames());

        List<String> operatorNames = new ArrayList<>(operatorList.size());
        for (SearchFieldOperatorName operatorName : operatorList) {
            operatorNames.add(operatorName.getQualifiedName());
        }

        // Sort in alphabetical order
        Collections.sort(operatorNames);

        return operatorNames;
    }

    @Override
    public Schema getSchemaForUpdateFlow(String typeName, Schema schema) {
        RecordTypeInfo recordTypeInfo = metaDataSource.getRecordType(typeName);
        TypeDesc typeDesc = metaDataSource.getTypeInfo(typeName);

        // We should check and add key fields:
        // internalId, externalId and scriptId (for custom record type)

        List<FieldDesc> fieldDescList = new ArrayList<>();
        Schema.Field internalIdField = getNsFieldByName(schema, "internalId");
        if (internalIdField == null) {
            FieldDesc fieldDesc = typeDesc.getField("internalId");
            fieldDescList.add(fieldDesc);
        }
        Schema.Field externalIdField = getNsFieldByName(schema, "externalId");
        if (externalIdField == null) {
            FieldDesc fieldDesc = typeDesc.getField("externalId");
            fieldDescList.add(fieldDesc);
        }
        if (recordTypeInfo instanceof CustomRecordTypeInfo) {
            Schema.Field scriptIdField = getNsFieldByName(schema, "scriptId");
            if (scriptIdField == null) {
                FieldDesc fieldDesc = typeDesc.getField("scriptId");
                if (fieldDesc != null) {
                    fieldDescList.add(fieldDesc);
                }
            }
        }

        // Create schema fields for mandatory fields.

        List<Schema.Field> fields = new ArrayList<>();
        Schema.Field f;
        if (!fieldDescList.isEmpty()) {
            Schema schemaToAdd = inferSchemaForType(typeName, fieldDescList);
            for (Schema.Field sourceField : schemaToAdd.getFields()) {
                f = copyField(sourceField);
                f.addProp(SchemaConstants.TALEND_FIELD_GENERATED, "true");
                f.addProp(SchemaConstants.TALEND_IS_LOCKED, "true");
                fields.add(f);
            }
        }

        return extendSchema(schema, typeName + "_FLOW", fields);
    }

    @Override
    public Schema getSchemaForDeleteFlow(String typeName, Schema schema) {
        RecordTypeInfo recordTypeInfo = metaDataSource.getRecordType(typeName);
        TypeDesc typeDesc = metaDataSource.getTypeInfo(typeName);

        // We should check and add key fields:
        // internalId, externalId and scriptId (for custom record type)

        List<FieldDesc> fieldDescList = new ArrayList<>();
        Schema.Field internalIdField = getNsFieldByName(schema, "internalId");
        if (internalIdField == null) {
            FieldDesc fieldDesc = typeDesc.getField("internalId");
            fieldDescList.add(fieldDesc);
        }
        Schema.Field externalIdField = getNsFieldByName(schema, "externalId");
        if (externalIdField == null) {
            FieldDesc fieldDesc = typeDesc.getField("externalId");
            fieldDescList.add(fieldDesc);
        }
        if (recordTypeInfo instanceof CustomRecordTypeInfo) {
            Schema.Field scriptIdField = getNsFieldByName(schema, "scriptId");
            if (scriptIdField == null) {
                FieldDesc fieldDesc = typeDesc.getField("scriptId");
                if (fieldDesc != null) {
                    fieldDescList.add(fieldDesc);
                }
            }
        }

        // Create schema fields for mandatory fields.

        List<Schema.Field> fields = new ArrayList<>();
        Schema.Field f;
        if (!fieldDescList.isEmpty()) {
            Schema schemaToAdd = inferSchemaForType(typeName, fieldDescList);
            for (Schema.Field sourceField : schemaToAdd.getFields()) {
                f = copyField(sourceField);
                f.addProp(SchemaConstants.TALEND_FIELD_GENERATED, "true");
                f.addProp(SchemaConstants.TALEND_IS_LOCKED, "true");
                fields.add(f);
            }
        }

        return extendSchema(schema, typeName + "_FLOW", fields);
    }

    @Override
    public Schema getSchemaForUpdateReject(String typeName, Schema schema) {
        return getSchemaForReject(schema, typeName + "_REJECT");
    }

    @Override
    public Schema getSchemaForDeleteReject(String typeName, Schema schema) {
        return getSchemaForReject(schema, typeName + "_REJECT");
    }

    /**
     * Get schema for outgoing reject flow.
     *
     * @param schema schema to be usd as base schema
     * @param newSchemaName name of new schema
     * @return schema
     */
    public Schema getSchemaForReject(Schema schema, String newSchemaName) {
        List<Schema.Field> fields = new ArrayList<>();

        // Add errorCode and errorMessage schema fields.

        Schema.Field f;

        f = new Schema.Field("errorCode", Schema.create(Schema.Type.STRING), null, (Object) null);
        f.addProp(SchemaConstants.TALEND_FIELD_GENERATED, "true");
        f.addProp(SchemaConstants.TALEND_IS_LOCKED, "true");
        fields.add(f);

        f = new Schema.Field("errorMessage", Schema.create(Schema.Type.STRING), null, (Object) null);
        f.addProp(SchemaConstants.TALEND_FIELD_GENERATED, "true");
        f.addProp(SchemaConstants.TALEND_IS_LOCKED, "true");
        fields.add(f);

        return extendSchema(schema, newSchemaName, fields);
    }

    /**
     * Infers an Avro schema for the given type. This can be an expensive operation so the schema
     * should be cached where possible. This is always an {@link Schema.Type#RECORD}.
     *
     * @param name name of a record.
     * @return the schema for data given from the object.
     */
    public static Schema inferSchemaForType(String name, List<FieldDesc> fieldDescList) {
        List<Schema.Field> fields = new ArrayList<>();

        for (FieldDesc fieldDesc : fieldDescList) {
            final String fieldName = fieldDesc.getName();
            final String avroFieldName = toInitialUpper(fieldName);

            Schema.Field avroField = new Schema.Field(avroFieldName, inferSchemaForField(fieldDesc), null,
                    (Object) null);

            // Add some Talend6 custom properties to the schema.

            Schema avroFieldSchema = AvroUtils.unwrapIfNullable(avroField.schema());

            avroField.addProp(SchemaConstants.TALEND_COLUMN_DB_COLUMN_NAME, fieldDesc.getName());

            if (AvroUtils.isSameType(avroFieldSchema, AvroUtils._string())) {
                if (fieldDesc.getLength() != 0) {
                    avroField.addProp(SchemaConstants.TALEND_COLUMN_DB_LENGTH,
                            String.valueOf(fieldDesc.getLength()));
                }
            }

            if (fieldDesc instanceof CustomFieldDesc) {
                CustomFieldDesc customFieldInfo = (CustomFieldDesc) fieldDesc;
                CustomFieldRefType customFieldRefType = customFieldInfo.getCustomFieldType();

                avroField.addProp(SchemaConstants.TALEND_COLUMN_DB_TYPE, customFieldRefType.getTypeName());

                if (customFieldRefType == CustomFieldRefType.DATE) {
                    avroField.addProp(SchemaConstants.TALEND_COLUMN_PATTERN, "yyyy-MM-dd'T'HH:mm:ss'.000Z'");
                }

                NsRef ref = customFieldInfo.getCustomizationRef();
                if (StringUtils.isNotEmpty(ref.getName())) {
                    avroField.addProp(NetSuiteSchemaConstants.TALEND6_COMMENT, ref.getName());
                }

            } else {
                Class<?> fieldType = fieldDesc.getValueType();

                avroField.addProp(SchemaConstants.TALEND_COLUMN_DB_TYPE, fieldType.getSimpleName());

                if (fieldType == XMLGregorianCalendar.class) {
                    avroField.addProp(SchemaConstants.TALEND_COLUMN_PATTERN, "yyyy-MM-dd'T'HH:mm:ss'.000Z'");
                }
            }

            if (avroField.defaultVal() != null) {
                avroField.addProp(SchemaConstants.TALEND_COLUMN_DEFAULT, String.valueOf(avroField.defaultVal()));
            }

            if (fieldDesc.isKey()) {
                avroField.addProp(SchemaConstants.TALEND_COLUMN_IS_KEY, Boolean.TRUE.toString());
            }

            fields.add(avroField);
        }

        return Schema.createRecord(name, null, null, false, fields);
    }

    /**
     * Infers an Avro schema for the given FieldDesc. This can be an expensive operation so the schema should be
     * cached where possible. The return type will be the Avro Schema that can contain the fieldDesc data without loss of
     * precision.
     *
     * @param fieldDesc the <code>FieldDesc</code> to analyse.
     * @return the schema for data that the fieldDesc describes.
     */
    public static Schema inferSchemaForField(FieldDesc fieldDesc) {
        Schema base;

        if (fieldDesc instanceof CustomFieldDesc) {
            CustomFieldDesc customFieldInfo = (CustomFieldDesc) fieldDesc;
            CustomFieldRefType customFieldRefType = customFieldInfo.getCustomFieldType();

            if (customFieldRefType == CustomFieldRefType.BOOLEAN) {
                base = AvroUtils._boolean();
            } else if (customFieldRefType == CustomFieldRefType.LONG) {
                base = AvroUtils._long();
            } else if (customFieldRefType == CustomFieldRefType.DOUBLE) {
                base = AvroUtils._double();
            } else if (customFieldRefType == CustomFieldRefType.DATE) {
                base = AvroUtils._logicalTimestamp();
            } else if (customFieldRefType == CustomFieldRefType.STRING) {
                base = AvroUtils._string();
            } else {
                base = AvroUtils._string();
            }

        } else {
            Class<?> fieldType = fieldDesc.getValueType();

            if (fieldType == Boolean.TYPE || fieldType == Boolean.class) {
                base = AvroUtils._boolean();
            } else if (fieldType == Integer.TYPE || fieldType == Integer.class) {
                base = AvroUtils._int();
            } else if (fieldType == Long.TYPE || fieldType == Long.class) {
                base = AvroUtils._long();
            } else if (fieldType == Float.TYPE || fieldType == Float.class) {
                base = AvroUtils._float();
            } else if (fieldType == Double.TYPE || fieldType == Double.class) {
                base = AvroUtils._double();
            } else if (fieldType == XMLGregorianCalendar.class) {
                base = AvroUtils._logicalTimestamp();
            } else if (fieldType == String.class) {
                base = AvroUtils._string();
            } else if (fieldType.isEnum()) {
                base = AvroUtils._string();
            } else {
                base = AvroUtils._string();
            }
        }

        base = fieldDesc.isNullable() ? AvroUtils.wrapAsNullable(base) : base;

        return base;
    }

    /**
     * Augment a given <code>Schema</code> with customization related meta data.
     *
     * @param metaDataSource source of meta data
     * @param schema schema to be augmented
     * @param recordTypeInfo information about record type to be used for augmentation
     * @param fieldDescList list of field descriptors to be used for augmentation
     */
    public static void augmentSchemaWithCustomMetaData(final MetaDataSource metaDataSource, final Schema schema,
            final RecordTypeInfo recordTypeInfo, final Collection<FieldDesc> fieldDescList) {

        if (recordTypeInfo == null) {
            // Not a record, do nothing
            return;
        }

        // Add custom record type meta data to a key field
        if (recordTypeInfo instanceof CustomRecordTypeInfo) {
            CustomRecordTypeInfo customRecordTypeInfo = (CustomRecordTypeInfo) recordTypeInfo;
            for (Schema.Field field : schema.getFields()) {
                writeCustomRecord(metaDataSource.getBasicMetaData(), field, customRecordTypeInfo);
            }
        }

        // Add custom field meta data to fields
        if (fieldDescList != null && !fieldDescList.isEmpty()) {
            Map<String, CustomFieldDesc> customFieldDescMap = getCustomFieldDescMap(fieldDescList);
            if (!customFieldDescMap.isEmpty()) {
                for (Schema.Field field : schema.getFields()) {
                    String nsFieldName = getNsFieldName(field);
                    CustomFieldDesc customFieldDesc = customFieldDescMap.get(nsFieldName);
                    if (customFieldDesc != null) {
                        writeCustomField(field, customFieldDesc);
                    }
                }
            }
        }
    }

    /**
     * Extend a schema with additional fields.
     *
     * @param sourceSchema source schema
     * @param newSchemaName name of new schema
     * @param fieldsToAdd fields to be added
     * @return new schema
     */
    public static Schema extendSchema(Schema sourceSchema, String newSchemaName, List<Schema.Field> fieldsToAdd) {
        Schema newSchema = Schema.createRecord(newSchemaName, sourceSchema.getDoc(), sourceSchema.getNamespace(),
                sourceSchema.isError());

        List<Schema.Field> copyFieldList = new ArrayList<>();
        for (Schema.Field se : sourceSchema.getFields()) {
            Schema.Field field = copyField(se);
            copyFieldList.add(field);
        }

        copyFieldList.addAll(fieldsToAdd);

        newSchema.setFields(copyFieldList);

        for (Map.Entry<String, Object> entry : sourceSchema.getObjectProps().entrySet()) {
            newSchema.addProp(entry.getKey(), entry.getValue());
        }

        return newSchema;
    }

    /**
     * Copy a schema field.
     *
     * @param sourceField source field to be copied
     * @return new field
     */
    public static Schema.Field copyField(final Schema.Field sourceField) {
        Schema.Field field = new Schema.Field(sourceField.name(), sourceField.schema(), sourceField.doc(),
                sourceField.defaultVal(), sourceField.order());
        field.getObjectProps().putAll(sourceField.getObjectProps());
        for (Map.Entry<String, Object> entry : sourceField.getObjectProps().entrySet()) {
            field.addProp(entry.getKey(), entry.getValue());
        }
        return field;
    }

    /**
     * Write custom record meta data to a given <code>JsonProperties</code>.
     *
     * @see NetSuiteSchemaConstants
     *
     * @param basicMetaData basic meta data
     * @param properties properties object which to write meta data to
     * @param recordTypeInfo information about record type to be used
     */
    public static void writeCustomRecord(BasicMetaData basicMetaData, JsonProperties properties,
            CustomRecordTypeInfo recordTypeInfo) {
        NsRef ref = recordTypeInfo.getCustomizationRef();
        RecordTypeDesc recordTypeDesc = recordTypeInfo.getRecordType();

        properties.addProp(NetSuiteSchemaConstants.NS_CUSTOM_RECORD, "true");
        properties.addProp(NetSuiteSchemaConstants.NS_CUSTOM_RECORD_SCRIPT_ID, ref.getScriptId());
        properties.addProp(NetSuiteSchemaConstants.NS_CUSTOM_RECORD_INTERNAL_ID, ref.getInternalId());
        properties.addProp(NetSuiteSchemaConstants.NS_CUSTOM_RECORD_CUSTOMIZATION_TYPE, ref.getType());
        properties.addProp(NetSuiteSchemaConstants.NS_CUSTOM_RECORD_TYPE, recordTypeDesc.getType());
    }

    /**
     * Read custom record meta data from a given <code>JsonProperties</code>.
     *
     * @see NetSuiteSchemaConstants
     *
     * @param basicMetaData basic meta data
     * @param properties properties object which to read meta data from
     * @return custom record type info or <code>null</code> if meta data was not found
     */
    public static CustomRecordTypeInfo readCustomRecord(BasicMetaData basicMetaData, JsonProperties properties) {
        String scriptId = properties.getProp(NetSuiteSchemaConstants.NS_CUSTOM_RECORD_SCRIPT_ID);
        if (StringUtils.isEmpty(scriptId)) {
            return null;
        }
        String internalId = properties.getProp(NetSuiteSchemaConstants.NS_CUSTOM_RECORD_INTERNAL_ID);
        String customizationType = properties.getProp(NetSuiteSchemaConstants.NS_CUSTOM_RECORD_CUSTOMIZATION_TYPE);
        String recordType = properties.getProp(NetSuiteSchemaConstants.NS_CUSTOM_RECORD_TYPE);

        NsRef ref = new NsRef();
        ref.setRefType(RefType.CUSTOMIZATION_REF);
        ref.setScriptId(scriptId);
        ref.setInternalId(internalId);
        ref.setType(customizationType);

        RecordTypeDesc recordTypeDesc = basicMetaData.getRecordType(recordType);
        CustomRecordTypeInfo recordTypeInfo = new CustomRecordTypeInfo(scriptId, recordTypeDesc, ref);

        return recordTypeInfo;
    }

    /**
     * Write custom field meta data to a given <code>JsonProperties</code>.
     *
     * @see NetSuiteSchemaConstants
     *
     * @param properties properties object which to write meta data to
     * @param fieldDesc information about custom field to be used
     */
    public static void writeCustomField(JsonProperties properties, CustomFieldDesc fieldDesc) {
        NsRef ref = fieldDesc.getCustomizationRef();
        CustomFieldRefType customFieldRefType = fieldDesc.getCustomFieldType();

        properties.addProp(NetSuiteSchemaConstants.NS_CUSTOM_FIELD, "true");
        properties.addProp(NetSuiteSchemaConstants.NS_CUSTOM_FIELD_SCRIPT_ID, ref.getScriptId());
        properties.addProp(NetSuiteSchemaConstants.NS_CUSTOM_FIELD_INTERNAL_ID, ref.getInternalId());
        properties.addProp(NetSuiteSchemaConstants.NS_CUSTOM_FIELD_CUSTOMIZATION_TYPE, ref.getType());
        properties.addProp(NetSuiteSchemaConstants.NS_CUSTOM_FIELD_TYPE, customFieldRefType.name());
    }

    /**
     * Read custom field meta data from a given <code>JsonProperties</code>.
     *
     * @see NetSuiteSchemaConstants
     *
     * @param properties properties object which to read meta data from
     * @return custom field info or <code>null</code> if meta data was not found
     */
    public static CustomFieldDesc readCustomField(JsonProperties properties) {
        String scriptId = properties.getProp(NetSuiteSchemaConstants.NS_CUSTOM_FIELD_SCRIPT_ID);
        if (StringUtils.isEmpty(scriptId)) {
            return null;
        }
        String internalId = properties.getProp(NetSuiteSchemaConstants.NS_CUSTOM_FIELD_INTERNAL_ID);
        String customizationType = properties.getProp(NetSuiteSchemaConstants.NS_CUSTOM_FIELD_CUSTOMIZATION_TYPE);
        String type = properties.getProp(NetSuiteSchemaConstants.NS_CUSTOM_FIELD_TYPE);

        NsRef ref = new NsRef();
        ref.setRefType(RefType.CUSTOMIZATION_REF);
        ref.setScriptId(scriptId);
        ref.setInternalId(internalId);
        ref.setType(customizationType);

        CustomFieldRefType customFieldRefType = CustomFieldRefType.valueOf(type);

        CustomFieldDesc fieldDesc = new CustomFieldDesc();
        fieldDesc.setCustomFieldType(customFieldRefType);
        fieldDesc.setCustomizationRef(ref);
        fieldDesc.setName(scriptId);
        fieldDesc.setValueType(getCustomFieldValueClass(customFieldRefType));
        fieldDesc.setNullable(true);

        return fieldDesc;
    }

    /**
     * Build and return map of custom field descriptors.
     *
     * @param fieldDescList list of custom field descriptors
     * @return map of custom field descriptors by names
     */
    public static Map<String, CustomFieldDesc> getCustomFieldDescMap(Collection<FieldDesc> fieldDescList) {
        Map<String, CustomFieldDesc> customFieldDescMap = new HashMap<>();
        for (FieldDesc fieldDesc : fieldDescList) {
            if (fieldDesc instanceof CustomFieldDesc) {
                CustomFieldDesc customFieldDesc = fieldDesc.asCustom();
                customFieldDescMap.put(customFieldDesc.getName(), customFieldDesc);
            }
        }
        return customFieldDescMap;
    }

    /**
     * Return type of value hold by a custom field.
     *
     * @param fieldDesc custom field descriptor
     * @return type of value
     */
    public static Class<?> getCustomFieldValueClass(CustomFieldDesc fieldDesc) {
        return getCustomFieldValueClass(fieldDesc.getCustomFieldType());
    }

    /**
     * Return type of value hold by a custom field with given <code>CustomFieldRefType</code>.
     *
     * @param customFieldRefType type of field
     * @return type of value
     */
    public static Class<?> getCustomFieldValueClass(CustomFieldRefType customFieldRefType) {
        Class<?> valueClass = null;
        switch (customFieldRefType) {
        case BOOLEAN:
            valueClass = Boolean.class;
            break;
        case STRING:
            valueClass = String.class;
            break;
        case LONG:
            valueClass = Long.class;
            break;
        case DOUBLE:
            valueClass = Double.class;
            break;
        case DATE:
            valueClass = XMLGregorianCalendar.class;
            break;
        case SELECT:
        case MULTI_SELECT:
            valueClass = String.class;
            break;
        }
        return valueClass;
    }

    /**
     * Return internal (NetSuite specific) name for a given <code>schema field</code>.
     *
     * @param field schema field
     * @return name
     */
    public static String getNsFieldName(Schema.Field field) {
        String name = field.getProp(SchemaConstants.TALEND_COLUMN_DB_COLUMN_NAME);
        return name != null ? toInitialLower(name) : toInitialLower(field.name());
    }

    /**
     * Find and return <code>schema field</code> by it's name.
     *
     * @param schema schema
     * @param fieldName name of field to be found
     * @return schema field or <code>null</code> if field was not found
     */
    public static Schema.Field getNsFieldByName(Schema schema, String fieldName) {
        for (Schema.Field field : schema.getFields()) {
            String nsFieldName = getNsFieldName(field);
            if (fieldName.equals(nsFieldName)) {
                return field;
            }
        }
        return null;
    }

    public static class FieldDescComparator implements Comparator<FieldDesc> {

        public static final FieldDescComparator INSTANCE = new FieldDescComparator();

        @Override
        public int compare(FieldDesc o1, FieldDesc o2) {
            int result = Boolean.compare(o1.isKey(), o2.isKey());
            if (result != 0) {
                return result * -1;
            }
            result = o1.getName().compareTo(o2.getName());
            return result;
        }

    }
}