/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import DFLT_DIALECTS from 'app/data/dialects.json';

import { EmptyBean, Bean } from './Beans';

import IgniteClusterDefaults from './defaults/Cluster.service';
import IgniteEventGroups from './defaults/Event-groups.service';
import IgniteCacheDefaults from './defaults/Cache.service';
import IgniteIGFSDefaults from './defaults/IGFS.service';

import JavaTypes from '../../../services/JavaTypes.service';
import VersionService from 'app/services/Version.service';

import isNil from 'lodash/isNil';
import {nonNil, nonEmpty} from 'app/utils/lodashMixins';

const clusterDflts = new IgniteClusterDefaults();
const cacheDflts = new IgniteCacheDefaults();
const igfsDflts = new IgniteIGFSDefaults();
const javaTypes = new JavaTypes(clusterDflts, cacheDflts, igfsDflts);
const versionService = new VersionService();

export default class IgniteConfigurationGenerator {
    static eventGrps = new IgniteEventGroups();

    static igniteConfigurationBean(cluster) {
        return new Bean('org.apache.ignite.configuration.IgniteConfiguration', 'cfg', cluster, clusterDflts);
    }

    static igfsConfigurationBean(igfs) {
        return new Bean('org.apache.ignite.configuration.FileSystemConfiguration', 'igfs', igfs, igfsDflts);
    }

    static cacheConfigurationBean(cache) {
        return new Bean('org.apache.ignite.configuration.CacheConfiguration', 'ccfg', cache, cacheDflts);
    }

    static domainConfigurationBean(domain) {
        return new Bean('org.apache.ignite.cache.QueryEntity', 'qryEntity', domain, cacheDflts);
    }

    static domainJdbcTypeBean(domain) {
        return new Bean('org.apache.ignite.cache.store.jdbc.JdbcType', 'type', domain);
    }

    static discoveryConfigurationBean(discovery) {
        return new Bean('org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi', 'discovery', discovery, clusterDflts.discovery);
    }

    /**
     * Function to generate ignite configuration.
     *
     * @param {Object} cluster Cluster to process.
     * @param {Object} targetVer Target version of configuration.
     * @param {Boolean} client Is client configuration.
     * @return {Bean} Generated ignite configuration.
     */
    static igniteConfiguration(cluster, targetVer, client) {
        const available = versionService.since.bind(versionService, targetVer.ignite);

        const cfg = this.igniteConfigurationBean(cluster);

        this.clusterGeneral(cluster, available, cfg, client);
        this.clusterAtomics(cluster.atomicConfiguration, available, cfg);
        this.clusterBinary(cluster.binaryConfiguration, cfg);
        this.clusterCacheKeyConfiguration(cluster.cacheKeyConfiguration, cfg);
        this.clusterCheckpoint(cluster, available, cluster.caches, cfg);

        if (available('2.3.0'))
            this.clusterClientConnector(cluster, available, cfg);

        this.clusterCollision(cluster.collision, cfg);
        this.clusterCommunication(cluster, available, cfg);
        this.clusterConnector(cluster.connector, cfg);

        // Since ignite 2.3
        if (available('2.3.0'))
            this.clusterDataStorageConfiguration(cluster.dataStorageConfiguration, available, cfg);

        this.clusterDeployment(cluster, available, cfg);
        this.clusterEvents(cluster, available, cfg);
        this.clusterFailover(cluster, available, cfg);
        this.clusterHadoop(cluster.hadoopConfiguration, cfg);
        this.clusterLoadBalancing(cluster, cfg);
        this.clusterLogger(cluster.logger, cfg);
        this.clusterMarshaller(cluster, available, cfg);

        // Since ignite 2.0 and deprecated in ignite 2.3
        if (available(['2.0.0', '2.3.0']))
            this.clusterMemory(cluster.memoryConfiguration, available, cfg);

        this.clusterMisc(cluster, available, cfg);
        this.clusterMetrics(cluster, available, cfg);
        this.clusterODBC(cluster.odbc, available, cfg);

        // Since ignite 2.1 deprecated in ignite 2.3
        if (available(['2.1.0', '2.3.0']))
            this.clusterPersistence(cluster.persistenceStoreConfiguration, available, cfg);

        if (available(['2.1.0', '2.3.0']))
            this.clusterQuery(cluster, available, cfg);

        this.clusterServiceConfiguration(cluster.serviceConfigurations, cluster.caches, cfg);
        this.clusterSsl(cluster, cfg);

        // Deprecated in ignite 2.0
        if (available(['1.0.0', '2.0.0']))
            this.clusterSwap(cluster, cfg);

        this.clusterPools(cluster, available, cfg);
        this.clusterTime(cluster, available, cfg);
        this.clusterTransactions(cluster.transactionConfiguration, cfg);
        this.clusterUserAttributes(cluster, cfg);

        this.clusterCaches(cluster, cluster.caches, cluster.igfss, available, client, cfg);

        if (!client)
            this.clusterIgfss(cluster.igfss, available, cfg);

        return cfg;
    }

    static dialectClsName(dialect) {
        return DFLT_DIALECTS[dialect] || 'Unknown database: ' + (dialect || 'Choose JDBC dialect');
    }

    static dataSourceBean(id, dialect) {
        let dsBean;

        switch (dialect) {
            case 'Generic':
                dsBean = new Bean('com.mchange.v2.c3p0.ComboPooledDataSource', id, {})
                    .property('jdbcUrl', `${id}.jdbc.url`, 'jdbc:your_database');

                break;
            case 'Oracle':
                dsBean = new Bean('oracle.jdbc.pool.OracleDataSource', id, {})
                    .property('URL', `${id}.jdbc.url`, 'jdbc:oracle:thin:@[host]:[port]:[database]');

                break;
            case 'DB2':
                dsBean = new Bean('com.ibm.db2.jcc.DB2DataSource', id, {})
                    .property('serverName', `${id}.jdbc.server_name`, 'YOUR_DATABASE_SERVER_NAME')
                    .propertyInt('portNumber', `${id}.jdbc.port_number`, 'YOUR_JDBC_PORT_NUMBER')
                    .property('databaseName', `${id}.jdbc.database_name`, 'YOUR_DATABASE_NAME')
                    .propertyInt('driverType', `${id}.jdbc.driver_type`, 'YOUR_JDBC_DRIVER_TYPE');

                break;
            case 'SQLServer':
                dsBean = new Bean('com.microsoft.sqlserver.jdbc.SQLServerDataSource', id, {})
                    .property('URL', `${id}.jdbc.url`, 'jdbc:sqlserver://[host]:[port][;databaseName=database]');

                break;
            case 'MySQL':
                dsBean = new Bean('com.mysql.jdbc.jdbc2.optional.MysqlDataSource', id, {})
                    .property('URL', `${id}.jdbc.url`, 'jdbc:mysql://[host]:[port]/[database]');

                break;
            case 'PostgreSQL':
                dsBean = new Bean('org.postgresql.ds.PGPoolingDataSource', id, {})
                    .property('url', `${id}.jdbc.url`, 'jdbc:postgresql://[host]:[port]/[database]');

                break;
            case 'H2':
                dsBean = new Bean('org.h2.jdbcx.JdbcDataSource', id, {})
                    .property('URL', `${id}.jdbc.url`, 'jdbc:h2:tcp://[host]/[database]');

                break;
            default:
        }

        if (dsBean) {
            dsBean.property('user', `${id}.jdbc.username`, 'YOUR_USER_NAME')
                .property('password', `${id}.jdbc.password`, 'YOUR_PASSWORD');
        }

        return dsBean;
    }

    // Generate general section.
    static clusterGeneral(cluster, available, cfg = this.igniteConfigurationBean(cluster), client = false) {
        if (client)
            cfg.prop('boolean', 'clientMode', true);

        if (available('2.0.0'))
            cfg.stringProperty('name', 'igniteInstanceName');
        else
            cfg.stringProperty('name', 'gridName');

        cfg.stringProperty('localHost');

        if (isNil(cluster.discovery))
            return cfg;

        const discovery = new Bean('org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi', 'discovery',
            cluster.discovery, clusterDflts.discovery);

        let ipFinder;

        switch (discovery.valueOf('kind')) {
            case 'Vm':
                ipFinder = new Bean('org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder',
                    'ipFinder', cluster.discovery.Vm, clusterDflts.discovery.Vm);

                ipFinder.collectionProperty('addrs', 'addresses', cluster.discovery.Vm.addresses);

                break;
            case 'Multicast':
                ipFinder = new Bean('org.apache.ignite.spi.discovery.tcp.ipfinder.multicast.TcpDiscoveryMulticastIpFinder',
                    'ipFinder', cluster.discovery.Multicast, clusterDflts.discovery.Multicast);

                ipFinder.stringProperty('multicastGroup')
                    .intProperty('multicastPort')
                    .intProperty('responseWaitTime')
                    .intProperty('addressRequestAttempts')
                    .stringProperty('localAddress')
                    .collectionProperty('addrs', 'addresses', cluster.discovery.Multicast.addresses);

                break;
            case 'S3':
                ipFinder = new Bean('org.apache.ignite.spi.discovery.tcp.ipfinder.s3.TcpDiscoveryS3IpFinder',
                    'ipFinder', cluster.discovery.S3, clusterDflts.discovery.S3);

                ipFinder.stringProperty('bucketName');

                if (available('2.4.0')) {
                    ipFinder.stringProperty('bucketEndpoint')
                        .stringProperty('SSEAlgorithm');
                }

                break;
            case 'Cloud':
                ipFinder = new Bean('org.apache.ignite.spi.discovery.tcp.ipfinder.cloud.TcpDiscoveryCloudIpFinder',
                    'ipFinder', cluster.discovery.Cloud, clusterDflts.discovery.Cloud);

                ipFinder.stringProperty('credential')
                    .pathProperty('credentialPath')
                    .stringProperty('identity')
                    .stringProperty('provider')
                    .collectionProperty('regions', 'regions', cluster.discovery.Cloud.regions)
                    .collectionProperty('zones', 'zones', cluster.discovery.Cloud.zones);

                break;
            case 'GoogleStorage':
                ipFinder = new Bean('org.apache.ignite.spi.discovery.tcp.ipfinder.gce.TcpDiscoveryGoogleStorageIpFinder',
                    'ipFinder', cluster.discovery.GoogleStorage, clusterDflts.discovery.GoogleStorage);

                ipFinder.stringProperty('projectName')
                    .stringProperty('bucketName')
                    .pathProperty('serviceAccountP12FilePath')
                    .stringProperty('serviceAccountId');

                break;
            case 'Jdbc':
                ipFinder = new Bean('org.apache.ignite.spi.discovery.tcp.ipfinder.jdbc.TcpDiscoveryJdbcIpFinder',
                    'ipFinder', cluster.discovery.Jdbc, clusterDflts.discovery.Jdbc);

                ipFinder.intProperty('initSchema');

                if (ipFinder.includes('dataSourceBean', 'dialect')) {
                    const id = ipFinder.valueOf('dataSourceBean');

                    ipFinder.dataSource(id, 'dataSource', this.dataSourceBean(id, ipFinder.valueOf('dialect')));
                }

                break;
            case 'SharedFs':
                ipFinder = new Bean('org.apache.ignite.spi.discovery.tcp.ipfinder.sharedfs.TcpDiscoverySharedFsIpFinder',
                    'ipFinder', cluster.discovery.SharedFs, clusterDflts.discovery.SharedFs);

                ipFinder.pathProperty('path');

                break;
            case 'ZooKeeper':
                const src = cluster.discovery.ZooKeeper;
                const dflt = clusterDflts.discovery.ZooKeeper;

                ipFinder = new Bean('org.apache.ignite.spi.discovery.tcp.ipfinder.zk.TcpDiscoveryZookeeperIpFinder',
                    'ipFinder', src, dflt);

                ipFinder.emptyBeanProperty('curator')
                    .stringProperty('zkConnectionString');

                const kind = _.get(src, 'retryPolicy.kind');

                if (kind) {
                    const policy = src.retryPolicy;

                    let retryPolicyBean;

                    switch (kind) {
                        case 'ExponentialBackoff':
                            retryPolicyBean = new Bean('org.apache.curator.retry.ExponentialBackoffRetry', null,
                                policy.ExponentialBackoff, dflt.ExponentialBackoff)
                                .intConstructorArgument('baseSleepTimeMs')
                                .intConstructorArgument('maxRetries')
                                .intConstructorArgument('maxSleepMs');

                            break;
                        case 'BoundedExponentialBackoff':
                            retryPolicyBean = new Bean('org.apache.curator.retry.BoundedExponentialBackoffRetry',
                                null, policy.BoundedExponentialBackoff, dflt.BoundedExponentialBackoffRetry)
                                .intConstructorArgument('baseSleepTimeMs')
                                .intConstructorArgument('maxSleepTimeMs')
                                .intConstructorArgument('maxRetries');

                            break;
                        case 'UntilElapsed':
                            retryPolicyBean = new Bean('org.apache.curator.retry.RetryUntilElapsed', null,
                                policy.UntilElapsed, dflt.UntilElapsed)
                                .intConstructorArgument('maxElapsedTimeMs')
                                .intConstructorArgument('sleepMsBetweenRetries');

                            break;

                        case 'NTimes':
                            retryPolicyBean = new Bean('org.apache.curator.retry.RetryNTimes', null,
                                policy.NTimes, dflt.NTimes)
                                .intConstructorArgument('n')
                                .intConstructorArgument('sleepMsBetweenRetries');

                            break;
                        case 'OneTime':
                            retryPolicyBean = new Bean('org.apache.curator.retry.RetryOneTime', null,
                                policy.OneTime, dflt.OneTime)
                                .intConstructorArgument('sleepMsBetweenRetry');

                            break;
                        case 'Forever':
                            retryPolicyBean = new Bean('org.apache.curator.retry.RetryForever', null,
                                policy.Forever, dflt.Forever)
                                .intConstructorArgument('retryIntervalMs');

                            break;
                        case 'Custom':
                            const className = _.get(policy, 'Custom.className');

                            if (nonEmpty(className))
                                retryPolicyBean = new EmptyBean(className);

                            break;
                        default:
                            // No-op.
                    }

                    if (retryPolicyBean)
                        ipFinder.beanProperty('retryPolicy', retryPolicyBean);
                }

                ipFinder.pathProperty('basePath')
                    .stringProperty('serviceName')
                    .boolProperty('allowDuplicateRegistrations');

                break;

            case 'Kubernetes':
                ipFinder = new Bean('org.apache.ignite.spi.discovery.tcp.ipfinder.kubernetes.TcpDiscoveryKubernetesIpFinder',
                    'ipFinder', cluster.discovery.Kubernetes, clusterDflts.discovery.Kubernetes);

                ipFinder.stringProperty('serviceName')
                    .stringProperty('namespace')
                    .stringProperty('masterUrl')
                    .pathProperty('accountToken');

                break;

            default:
                // No-op.
        }

        if (ipFinder)
            discovery.beanProperty('ipFinder', ipFinder);

        this.clusterDiscovery(cluster.discovery, available, cfg, discovery);

        return cfg;
    }

    static igfsDataCache(igfs, available) {
        return this.cacheConfiguration({
            name: igfs.name + '-data',
            cacheMode: 'PARTITIONED',
            atomicityMode: 'TRANSACTIONAL',
            writeSynchronizationMode: 'FULL_SYNC',
            backups: 0,
            igfsAffinnityGroupSize: igfs.affinnityGroupSize || 512
        }, available);
    }

    static igfsMetaCache(igfs, available) {
        return this.cacheConfiguration({
            name: igfs.name + '-meta',
            cacheMode: 'REPLICATED',
            atomicityMode: 'TRANSACTIONAL',
            writeSynchronizationMode: 'FULL_SYNC'
        }, available);
    }

    static clusterCaches(cluster, caches, igfss, available, client, cfg = this.igniteConfigurationBean(cluster)) {
        const ccfgs = _.map(caches, (cache) => this.cacheConfiguration(cache, available));

        if (!client) {
            _.forEach(igfss, (igfs) => {
                ccfgs.push(this.igfsDataCache(igfs, available));
                ccfgs.push(this.igfsMetaCache(igfs, available));
            });
        }

        cfg.varArgProperty('ccfgs', 'cacheConfiguration', ccfgs, 'org.apache.ignite.configuration.CacheConfiguration');

        return cfg;
    }

    // Generate atomics group.
    static clusterAtomics(atomics, available, cfg = this.igniteConfigurationBean()) {
        const acfg = new Bean('org.apache.ignite.configuration.AtomicConfiguration', 'atomicCfg',
            atomics, clusterDflts.atomics);

        acfg.enumProperty('cacheMode')
            .intProperty('atomicSequenceReserveSize');

        if (acfg.valueOf('cacheMode') === 'PARTITIONED')
            acfg.intProperty('backups');

        if (available('2.1.0') && nonNil(atomics))
            this.affinity(atomics.affinity, acfg);

        if (acfg.isEmpty())
            return cfg;

        cfg.beanProperty('atomicConfiguration', acfg);

        return cfg;
    }

    // Generate binary group.
    static clusterBinary(binary, cfg = this.igniteConfigurationBean()) {
        const binaryCfg = new Bean('org.apache.ignite.configuration.BinaryConfiguration', 'binaryCfg',
            binary, clusterDflts.binary);

        binaryCfg.emptyBeanProperty('idMapper')
            .emptyBeanProperty('nameMapper')
            .emptyBeanProperty('serializer');

        const typeCfgs = [];

        _.forEach(binary.typeConfigurations, (type) => {
            const typeCfg = new Bean('org.apache.ignite.binary.BinaryTypeConfiguration',
                javaTypes.toJavaName('binaryType', type.typeName), type, clusterDflts.binary.typeConfigurations);

            typeCfg.stringProperty('typeName')
                .emptyBeanProperty('idMapper')
                .emptyBeanProperty('nameMapper')
                .emptyBeanProperty('serializer')
                .intProperty('enum');

            if (typeCfg.nonEmpty())
                typeCfgs.push(typeCfg);
        });

        binaryCfg.collectionProperty('types', 'typeConfigurations', typeCfgs, 'org.apache.ignite.binary.BinaryTypeConfiguration')
            .boolProperty('compactFooter');

        if (binaryCfg.isEmpty())
            return cfg;

        cfg.beanProperty('binaryConfiguration', binaryCfg);

        return cfg;
    }

    // Generate cache key configurations.
    static clusterCacheKeyConfiguration(keyCfgs, cfg = this.igniteConfigurationBean()) {
        const items = _.reduce(keyCfgs, (acc, keyCfg) => {
            if (keyCfg.typeName && keyCfg.affinityKeyFieldName) {
                acc.push(new Bean('org.apache.ignite.cache.CacheKeyConfiguration', null, keyCfg)
                    .stringConstructorArgument('typeName')
                    .stringConstructorArgument('affinityKeyFieldName'));
            }

            return acc;
        }, []);

        if (_.isEmpty(items))
            return cfg;

        cfg.arrayProperty('cacheKeyConfiguration', 'cacheKeyConfiguration', items,
            'org.apache.ignite.cache.CacheKeyConfiguration');

        return cfg;
    }

    // Generate checkpoint configurations.
    static clusterCheckpoint(cluster, available, caches, cfg = this.igniteConfigurationBean()) {
        const cfgs = _.filter(_.map(cluster.checkpointSpi, (spi) => {
            switch (_.get(spi, 'kind')) {
                case 'FS':
                    const fsBean = new Bean('org.apache.ignite.spi.checkpoint.sharedfs.SharedFsCheckpointSpi',
                        'checkpointSpiFs', spi.FS);

                    fsBean.collectionProperty('directoryPaths', 'directoryPaths', _.get(spi, 'FS.directoryPaths'))
                        .emptyBeanProperty('checkpointListener');

                    return fsBean;

                case 'Cache':
                    const cacheBean = new Bean('org.apache.ignite.spi.checkpoint.cache.CacheCheckpointSpi',
                        'checkpointSpiCache', spi.Cache);

                    const curCache = _.get(spi, 'Cache.cache');

                    const cache = _.find(caches, (c) => curCache && (c._id === curCache || _.get(c, 'cache._id') === curCache));

                    if (cache)
                        cacheBean.prop('java.lang.String', 'cacheName', cache.name || cache.cache.name);

                    cacheBean.stringProperty('cacheName')
                        .emptyBeanProperty('checkpointListener');

                    return cacheBean;

                case 'S3':
                    const s3Bean = new Bean('org.apache.ignite.spi.checkpoint.s3.S3CheckpointSpi',
                        'checkpointSpiS3', spi.S3, clusterDflts.checkpointSpi.S3);

                    let credentialsBean = null;

                    switch (_.get(spi.S3, 'awsCredentials.kind')) {
                        case 'Basic':
                            credentialsBean = new Bean('com.amazonaws.auth.BasicAWSCredentials', 'awsCredentials', {});

                            credentialsBean.propertyConstructorArgument('checkpoint.s3.credentials.accessKey', 'YOUR_S3_ACCESS_KEY')
                                .propertyConstructorArgument('checkpoint.s3.credentials.secretKey', 'YOUR_S3_SECRET_KEY');

                            break;

                        case 'Properties':
                            credentialsBean = new Bean('com.amazonaws.auth.PropertiesCredentials', 'awsCredentials', {});

                            const fileBean = new Bean('java.io.File', '', spi.S3.awsCredentials.Properties)
                                .pathConstructorArgument('path');

                            if (fileBean.nonEmpty())
                                credentialsBean.beanConstructorArgument('file', fileBean);

                            break;

                        case 'Anonymous':
                            credentialsBean = new Bean('com.amazonaws.auth.AnonymousAWSCredentials', 'awsCredentials', {});

                            break;

                        case 'BasicSession':
                            credentialsBean = new Bean('com.amazonaws.auth.BasicSessionCredentials', 'awsCredentials', {});

                            // TODO 2054 Arguments in one line is very long string.
                            credentialsBean.propertyConstructorArgument('checkpoint.s3.credentials.accessKey')
                                .propertyConstructorArgument('checkpoint.s3.credentials.secretKey')
                                .propertyConstructorArgument('checkpoint.s3.credentials.sessionToken');

                            break;

                        case 'Custom':
                            const className = _.get(spi.S3.awsCredentials, 'Custom.className');

                            if (className)
                                credentialsBean = new Bean(className, 'awsCredentials', {});

                            break;

                        default:
                            break;
                    }

                    if (credentialsBean)
                        s3Bean.beanProperty('awsCredentials', credentialsBean);

                    s3Bean.stringProperty('bucketNameSuffix');

                    if (available('2.4.0')) {
                        s3Bean.stringProperty('bucketEndpoint')
                            .stringProperty('SSEAlgorithm');
                    }

                    const clientBean = new Bean('com.amazonaws.ClientConfiguration', 'clientCfg', spi.S3.clientConfiguration,
                        clusterDflts.checkpointSpi.S3.clientConfiguration);

                    clientBean.enumProperty('protocol')
                        .intProperty('maxConnections')
                        .stringProperty('userAgentPrefix')
                        .stringProperty('userAgentSuffix');

                    const locAddr = new Bean('java.net.InetAddress', '', spi.S3.clientConfiguration)
                        .factoryMethod('getByName')
                        .stringConstructorArgument('localAddress');

                    if (locAddr.nonEmpty())
                        clientBean.beanProperty('localAddress', locAddr);

                    clientBean.stringProperty('proxyHost')
                        .intProperty('proxyPort')
                        .stringProperty('proxyUsername');

                    const userName = clientBean.valueOf('proxyUsername');

                    if (userName)
                        clientBean.property('proxyPassword', `checkpoint.s3.proxy.${userName}.password`);

                    clientBean.stringProperty('proxyDomain')
                        .stringProperty('proxyWorkstation')
                        .stringProperty('nonProxyHosts');

                    const retryPolicy = spi.S3.clientConfiguration.retryPolicy;

                    if (retryPolicy) {
                        const kind = retryPolicy.kind;

                        const policy = retryPolicy[kind];

                        let retryBean;

                        switch (kind) {
                            case 'Default':
                                retryBean = new Bean('com.amazonaws.retry.RetryPolicy', 'retryPolicy', {
                                    retryCondition: 'DEFAULT_RETRY_CONDITION',
                                    backoffStrategy: 'DEFAULT_BACKOFF_STRATEGY',
                                    maxErrorRetry: 'DEFAULT_MAX_ERROR_RETRY',
                                    honorMaxErrorRetryInClientConfig: true
                                }, clusterDflts.checkpointSpi.S3.clientConfiguration.retryPolicy);

                                retryBean.constantConstructorArgument('retryCondition')
                                    .constantConstructorArgument('backoffStrategy')
                                    .constantConstructorArgument('maxErrorRetry')
                                    .constructorArgument('java.lang.Boolean', retryBean.valueOf('honorMaxErrorRetryInClientConfig'));

                                break;

                            case 'DefaultMaxRetries':
                                retryBean = new Bean('com.amazonaws.retry.RetryPolicy', 'retryPolicy', {
                                    retryCondition: 'DEFAULT_RETRY_CONDITION',
                                    backoffStrategy: 'DEFAULT_BACKOFF_STRATEGY',
                                    maxErrorRetry: _.get(policy, 'maxErrorRetry') || -1,
                                    honorMaxErrorRetryInClientConfig: false
                                }, clusterDflts.checkpointSpi.S3.clientConfiguration.retryPolicy);

                                retryBean.constantConstructorArgument('retryCondition')
                                    .constantConstructorArgument('backoffStrategy')
                                    .constructorArgument('java.lang.Integer', retryBean.valueOf('maxErrorRetry'))
                                    .constructorArgument('java.lang.Boolean', retryBean.valueOf('honorMaxErrorRetryInClientConfig'));

                                break;

                            case 'DynamoDB':
                                retryBean = new Bean('com.amazonaws.retry.RetryPolicy', 'retryPolicy', {
                                    retryCondition: 'DEFAULT_RETRY_CONDITION',
                                    backoffStrategy: 'DYNAMODB_DEFAULT_BACKOFF_STRATEGY',
                                    maxErrorRetry: 'DYNAMODB_DEFAULT_MAX_ERROR_RETRY',
                                    honorMaxErrorRetryInClientConfig: true
                                }, clusterDflts.checkpointSpi.S3.clientConfiguration.retryPolicy);

                                retryBean.constantConstructorArgument('retryCondition')
                                    .constantConstructorArgument('backoffStrategy')
                                    .constantConstructorArgument('maxErrorRetry')
                                    .constructorArgument('java.lang.Boolean', retryBean.valueOf('honorMaxErrorRetryInClientConfig'));

                                break;

                            case 'DynamoDBMaxRetries':
                                retryBean = new Bean('com.amazonaws.retry.RetryPolicy', 'retryPolicy', {
                                    retryCondition: 'DEFAULT_RETRY_CONDITION',
                                    backoffStrategy: 'DYNAMODB_DEFAULT_BACKOFF_STRATEGY',
                                    maxErrorRetry: _.get(policy, 'maxErrorRetry') || -1,
                                    honorMaxErrorRetryInClientConfig: false
                                }, clusterDflts.checkpointSpi.S3.clientConfiguration.retryPolicy);

                                retryBean.constantConstructorArgument('retryCondition')
                                    .constantConstructorArgument('backoffStrategy')
                                    .constructorArgument('java.lang.Integer', retryBean.valueOf('maxErrorRetry'))
                                    .constructorArgument('java.lang.Boolean', retryBean.valueOf('honorMaxErrorRetryInClientConfig'));

                                break;

                            case 'Custom':
                                retryBean = new Bean('com.amazonaws.retry.RetryPolicy', 'retryPolicy', policy,
                                    clusterDflts.checkpointSpi.S3.clientConfiguration.retryPolicy);

                                retryBean.beanConstructorArgument('retryCondition', retryBean.valueOf('retryCondition') ? new EmptyBean(retryBean.valueOf('retryCondition')) : null)
                                    .beanConstructorArgument('backoffStrategy', retryBean.valueOf('backoffStrategy') ? new EmptyBean(retryBean.valueOf('backoffStrategy')) : null)
                                    .constructorArgument('java.lang.Integer', retryBean.valueOf('maxErrorRetry'))
                                    .constructorArgument('java.lang.Boolean', retryBean.valueOf('honorMaxErrorRetryInClientConfig'));

                                break;

                            default:
                                break;
                        }

                        if (retryBean)
                            clientBean.beanProperty('retryPolicy', retryBean);
                    }

                    clientBean.intProperty('maxErrorRetry')
                        .intProperty('socketTimeout')
                        .intProperty('connectionTimeout')
                        .intProperty('requestTimeout')
                        .stringProperty('signerOverride')
                        .longProperty('connectionTTL')
                        .longProperty('connectionMaxIdleMillis')
                        .emptyBeanProperty('dnsResolver')
                        .intProperty('responseMetadataCacheSize')
                        .emptyBeanProperty('secureRandom')
                        .intProperty('clientExecutionTimeout')
                        .boolProperty('useReaper')
                        .boolProperty('cacheResponseMetadata')
                        .boolProperty('useExpectContinue')
                        .boolProperty('useThrottleRetries')
                        .boolProperty('useGzip')
                        .boolProperty('preemptiveBasicProxyAuth')
                        .boolProperty('useTcpKeepAlive');

                    if (clientBean.nonEmpty())
                        s3Bean.beanProperty('clientConfiguration', clientBean);

                    s3Bean.emptyBeanProperty('checkpointListener');

                    return s3Bean;

                case 'JDBC':
                    const jdbcBean = new Bean('org.apache.ignite.spi.checkpoint.jdbc.JdbcCheckpointSpi',
                        'checkpointSpiJdbc', spi.JDBC, clusterDflts.checkpointSpi.JDBC);

                    const id = jdbcBean.valueOf('dataSourceBean');
                    const dialect = _.get(spi.JDBC, 'dialect');

                    jdbcBean.dataSource(id, 'dataSource', this.dataSourceBean(id, dialect));

                    if (!_.isEmpty(jdbcBean.valueOf('user'))) {
                        jdbcBean.stringProperty('user')
                            .property('pwd', `checkpoint.${jdbcBean.valueOf('dataSourceBean')}.${jdbcBean.valueOf('user')}.jdbc.password`, 'YOUR_PASSWORD');
                    }

                    jdbcBean.stringProperty('checkpointTableName')
                        .stringProperty('keyFieldName')
                        .stringProperty('keyFieldType')
                        .stringProperty('valueFieldName')
                        .stringProperty('valueFieldType')
                        .stringProperty('expireDateFieldName')
                        .stringProperty('expireDateFieldType')
                        .intProperty('numberOfRetries')
                        .emptyBeanProperty('checkpointListener');

                    return jdbcBean;

                case 'Custom':
                    const clsName = _.get(spi, 'Custom.className');

                    if (clsName)
                        return new Bean(clsName, 'checkpointSpiCustom', spi.Cache);

                    return null;

                default:
                    return null;
            }
        }), (checkpointBean) => nonNil(checkpointBean));

        cfg.arrayProperty('checkpointSpi', 'checkpointSpi', cfgs, 'org.apache.ignite.spi.checkpoint.CheckpointSpi');

        return cfg;
    }

    // Generate cluster query group.
    static clusterClientConnector(cluster, available, cfg = this.igniteConfigurationBean(cluster)) {
        if (!available('2.3.0'))
            return cfg;

        cfg.longProperty('longQueryWarningTimeout');

        if (_.get(cluster, 'clientConnectorConfiguration.enabled') !== true)
            return cfg;

        const bean = new Bean('org.apache.ignite.configuration.ClientConnectorConfiguration', 'cliConnCfg',
            cluster.clientConnectorConfiguration, clusterDflts.clientConnectorConfiguration);

        bean.stringProperty('host')
            .intProperty('port')
            .intProperty('portRange')
            .intProperty('socketSendBufferSize')
            .intProperty('socketReceiveBufferSize')
            .intProperty('maxOpenCursorsPerConnection')
            .intProperty('threadPoolSize')
            .boolProperty('tcpNoDelay');

        if (available('2.4.0')) {
            bean.longProperty('idleTimeout')
                .boolProperty('jdbcEnabled')
                .boolProperty('odbcEnabled')
                .boolProperty('thinClientEnabled');
        }

        if (available('2.5.0')) {
            bean.boolProperty('sslEnabled')
                .boolProperty('sslClientAuth')
                .boolProperty('useIgniteSslContextFactory')
                .emptyBeanProperty('sslContextFactory');
        }

        cfg.beanProperty('clientConnectorConfiguration', bean);

        return cfg;
    }

    // Generate collision group.
    static clusterCollision(collision, cfg = this.igniteConfigurationBean()) {
        let colSpi;

        switch (_.get(collision, 'kind')) {
            case 'JobStealing':
                colSpi = new Bean('org.apache.ignite.spi.collision.jobstealing.JobStealingCollisionSpi',
                    'colSpi', collision.JobStealing, clusterDflts.collision.JobStealing);

                colSpi.intProperty('activeJobsThreshold')
                    .intProperty('waitJobsThreshold')
                    .longProperty('messageExpireTime')
                    .intProperty('maximumStealingAttempts')
                    .boolProperty('stealingEnabled')
                    .emptyBeanProperty('externalCollisionListener')
                    .mapProperty('stealingAttrs', 'stealingAttributes');

                break;
            case 'FifoQueue':
                colSpi = new Bean('org.apache.ignite.spi.collision.fifoqueue.FifoQueueCollisionSpi',
                    'colSpi', collision.FifoQueue, clusterDflts.collision.FifoQueue);

                colSpi.intProperty('parallelJobsNumber')
                    .intProperty('waitingJobsNumber');

                break;
            case 'PriorityQueue':
                colSpi = new Bean('org.apache.ignite.spi.collision.priorityqueue.PriorityQueueCollisionSpi',
                    'colSpi', collision.PriorityQueue, clusterDflts.collision.PriorityQueue);

                colSpi.intProperty('parallelJobsNumber')
                    .intProperty('waitingJobsNumber')
                    .stringProperty('priorityAttributeKey')
                    .stringProperty('jobPriorityAttributeKey')
                    .intProperty('defaultPriority')
                    .intProperty('starvationIncrement')
                    .boolProperty('starvationPreventionEnabled');

                break;
            case 'Custom':
                if (nonNil(_.get(collision, 'Custom.class')))
                    colSpi = new EmptyBean(collision.Custom.class);

                break;
            default:
                return cfg;
        }

        if (nonNil(colSpi))
            cfg.beanProperty('collisionSpi', colSpi);

        return cfg;
    }

    // Generate communication group.
    static clusterCommunication(cluster, available, cfg = this.igniteConfigurationBean(cluster)) {
        const commSpi = new Bean('org.apache.ignite.spi.communication.tcp.TcpCommunicationSpi', 'communicationSpi',
            cluster.communication, clusterDflts.communication);

        commSpi.emptyBeanProperty('listener')
            .stringProperty('localAddress')
            .intProperty('localPort')
            .intProperty('localPortRange')
            .intProperty('sharedMemoryPort')
            .intProperty('directBuffer')
            .intProperty('directSendBuffer')
            .longProperty('idleConnectionTimeout')
            .longProperty('connectTimeout')
            .longProperty('maxConnectTimeout')
            .intProperty('reconnectCount')
            .intProperty('socketSendBuffer')
            .intProperty('socketReceiveBuffer')
            .intProperty('messageQueueLimit')
            .intProperty('slowClientQueueLimit')
            .intProperty('tcpNoDelay')
            .intProperty('ackSendThreshold')
            .intProperty('unacknowledgedMessagesBufferSize')
            .longProperty('socketWriteTimeout')
            .intProperty('selectorsCount')
            .emptyBeanProperty('addressResolver');

        if (commSpi.nonEmpty())
            cfg.beanProperty('communicationSpi', commSpi);

        cfg.longProperty('networkTimeout')
            .longProperty('networkSendRetryDelay')
            .intProperty('networkSendRetryCount');

        if (available(['1.0.0', '2.3.0']))
            cfg.longProperty('discoveryStartupDelay');

        return cfg;
    }

    // Generate REST access configuration.
    static clusterConnector(connector, cfg = this.igniteConfigurationBean()) {
        const connCfg = new Bean('org.apache.ignite.configuration.ConnectorConfiguration',
            'connectorConfiguration', connector, clusterDflts.connector);

        if (connCfg.valueOf('enabled')) {
            connCfg.pathProperty('jettyPath')
                .stringProperty('host')
                .intProperty('port')
                .intProperty('portRange')
                .longProperty('idleTimeout')
                .longProperty('idleQueryCursorTimeout')
                .longProperty('idleQueryCursorCheckFrequency')
                .intProperty('receiveBufferSize')
                .intProperty('sendBufferSize')
                .intProperty('sendQueueLimit')
                .intProperty('directBuffer')
                .intProperty('noDelay')
                .intProperty('selectorCount')
                .intProperty('threadPoolSize')
                .emptyBeanProperty('messageInterceptor')
                .stringProperty('secretKey');

            if (connCfg.valueOf('sslEnabled')) {
                connCfg.intProperty('sslClientAuth')
                    .emptyBeanProperty('sslFactory');
            }

            if (connCfg.nonEmpty())
                cfg.beanProperty('connectorConfiguration', connCfg);
        }

        return cfg;
    }

    // Generate deployment group.
    static clusterDeployment(cluster, available, cfg = this.igniteConfigurationBean(cluster)) {
        cfg.enumProperty('deploymentMode')
            .boolProperty('peerClassLoadingEnabled');

        if (cfg.valueOf('peerClassLoadingEnabled')) {
            cfg.intProperty('peerClassLoadingMissedResourcesCacheSize')
                .intProperty('peerClassLoadingThreadPoolSize')
                .varArgProperty('p2pLocClsPathExcl', 'peerClassLoadingLocalClassPathExclude',
                    cluster.peerClassLoadingLocalClassPathExclude);
        }

        // Since ignite 2.0
        if (available('2.0.0'))
            cfg.emptyBeanProperty('classLoader');

        let deploymentBean = null;

        switch (_.get(cluster, 'deploymentSpi.kind')) {
            case 'URI':
                const uriDeployment = cluster.deploymentSpi.URI;

                deploymentBean = new Bean('org.apache.ignite.spi.deployment.uri.UriDeploymentSpi', 'deploymentSpi', uriDeployment);

                const scanners = _.map(uriDeployment.scanners, (scanner) => new EmptyBean(scanner));

                deploymentBean.collectionProperty('uriList', 'uriList', uriDeployment.uriList)
                    .stringProperty('temporaryDirectoryPath')
                    .varArgProperty('scanners', 'scanners', scanners,
                        'org.apache.ignite.spi.deployment.uri.scanners.UriDeploymentScanner')
                    .emptyBeanProperty('listener')
                    .boolProperty('checkMd5')
                    .boolProperty('encodeUri');

                cfg.beanProperty('deploymentSpi', deploymentBean);

                break;

            case 'Local':
                deploymentBean = new Bean('org.apache.ignite.spi.deployment.local.LocalDeploymentSpi', 'deploymentSpi', cluster.deploymentSpi.Local);

                deploymentBean.emptyBeanProperty('listener');

                cfg.beanProperty('deploymentSpi', deploymentBean);

                break;

            case 'Custom':
                cfg.emptyBeanProperty('deploymentSpi.Custom.className');

                break;

            default:
                // No-op.
        }

        return cfg;
    }

    // Generate discovery group.
    static clusterDiscovery(discovery, available, cfg = this.igniteConfigurationBean(), discoSpi = this.discoveryConfigurationBean(discovery)) {
        discoSpi.stringProperty('localAddress')
            .intProperty('localPort')
            .intProperty('localPortRange')
            .emptyBeanProperty('addressResolver')
            .longProperty('socketTimeout')
            .longProperty('ackTimeout')
            .longProperty('maxAckTimeout')
            .longProperty('networkTimeout')
            .longProperty('joinTimeout')
            .intProperty('threadPriority');

        // Removed in ignite 2.0
        if (available(['1.0.0', '2.0.0'])) {
            discoSpi.intProperty('heartbeatFrequency')
                .intProperty('maxMissedHeartbeats')
                .intProperty('maxMissedClientHeartbeats');
        }

        discoSpi.longProperty('topHistorySize')
            .emptyBeanProperty('listener')
            .emptyBeanProperty('dataExchange')
            .emptyBeanProperty('metricsProvider')
            .intProperty('reconnectCount')
            .longProperty('statisticsPrintFrequency')
            .longProperty('ipFinderCleanFrequency')
            .emptyBeanProperty('authenticator')
            .intProperty('forceServerMode')
            .intProperty('clientReconnectDisabled');

        if (discoSpi.nonEmpty())
            cfg.beanProperty('discoverySpi', discoSpi);

        return discoSpi;
    }

    // Execute event filtration in accordance to generated project version.
    static filterEvents(eventGrps, available) {
        if (available('2.0.0')) {
            return _.reduce(eventGrps, (acc, eventGrp) => {
                switch (eventGrp.value) {
                    case 'EVTS_SWAPSPACE':
                        // Removed.

                        break;
                    case 'EVTS_CACHE':
                        const eventGrpX2 = _.cloneDeep(eventGrp);

                        eventGrpX2.events = _.filter(eventGrpX2.events, (ev) =>
                            !_.includes(['EVT_CACHE_OBJECT_SWAPPED', 'EVT_CACHE_OBJECT_UNSWAPPED'], ev));

                        acc.push(eventGrpX2);

                        break;
                    default:
                        acc.push(eventGrp);
                }

                return acc;
            }, []);
        }

        return eventGrps;
    }

    // Generate events group.
    static clusterEvents(cluster, available, cfg = this.igniteConfigurationBean(cluster)) {
        const eventStorage = cluster.eventStorage;

        let eventStorageBean = null;

        switch (_.get(eventStorage, 'kind')) {
            case 'Memory':
                eventStorageBean = new Bean('org.apache.ignite.spi.eventstorage.memory.MemoryEventStorageSpi', 'eventStorage', eventStorage.Memory, clusterDflts.eventStorage.Memory);

                eventStorageBean.longProperty('expireAgeMs')
                    .longProperty('expireCount')
                    .emptyBeanProperty('filter');

                break;

            case 'Custom':
                const className = _.get(eventStorage, 'Custom.className');

                if (className)
                    eventStorageBean = new EmptyBean(className);

                break;

            default:
                // No-op.
        }

        if (eventStorageBean) {
            if (!eventStorageBean.isEmpty() || !available(['1.0.0', '2.0.0']))
                cfg.beanProperty('eventStorageSpi', eventStorageBean);

            if (nonEmpty(cluster.includeEventTypes)) {
                const eventGrps = _.filter(this.eventGrps, ({value}) => _.includes(cluster.includeEventTypes, value));

                cfg.eventTypes('evts', 'includeEventTypes', this.filterEvents(eventGrps, available));
            }
        }

        return cfg;
    }

    // Generate failover group.
    static clusterFailover(cluster, available, cfg = this.igniteConfigurationBean(cluster)) {
        const spis = [];

        // Since ignite 2.0
        if (available('2.0.0')) {
            cfg.longProperty('failureDetectionTimeout')
                .longProperty('clientFailureDetectionTimeout');
        }

        _.forEach(cluster.failoverSpi, (spi) => {
            let failoverSpi;

            switch (_.get(spi, 'kind')) {
                case 'JobStealing':
                    failoverSpi = new Bean('org.apache.ignite.spi.failover.jobstealing.JobStealingFailoverSpi',
                        'failoverSpi', spi.JobStealing, clusterDflts.failoverSpi.JobStealing);

                    failoverSpi.intProperty('maximumFailoverAttempts');

                    break;
                case 'Never':
                    failoverSpi = new Bean('org.apache.ignite.spi.failover.never.NeverFailoverSpi',
                        'failoverSpi', spi.Never);

                    break;
                case 'Always':
                    failoverSpi = new Bean('org.apache.ignite.spi.failover.always.AlwaysFailoverSpi',
                        'failoverSpi', spi.Always, clusterDflts.failoverSpi.Always);

                    failoverSpi.intProperty('maximumFailoverAttempts');

                    break;
                case 'Custom':
                    const className = _.get(spi, 'Custom.class');

                    if (className)
                        failoverSpi = new EmptyBean(className);

                    break;
                default:
                    // No-op.
            }

            if (failoverSpi)
                spis.push(failoverSpi);
        });

        if (spis.length)
            cfg.arrayProperty('failoverSpi', 'failoverSpi', spis, 'org.apache.ignite.spi.failover.FailoverSpi');

        return cfg;
    }

    // Generate failover group.
    static clusterHadoop(hadoop, cfg = this.igniteConfigurationBean()) {
        const hadoopBean = new Bean('org.apache.ignite.configuration.HadoopConfiguration', 'hadoop', hadoop, clusterDflts.hadoopConfiguration);

        let plannerBean;

        switch (_.get(hadoop, 'mapReducePlanner.kind')) {
            case 'Weighted':
                plannerBean = new Bean('org.apache.ignite.hadoop.mapreduce.IgniteHadoopWeightedMapReducePlanner', 'planner',
                    _.get(hadoop, 'mapReducePlanner.Weighted'), clusterDflts.hadoopConfiguration.mapReducePlanner.Weighted);

                plannerBean.intProperty('localMapperWeight')
                    .intProperty('remoteMapperWeight')
                    .intProperty('localReducerWeight')
                    .intProperty('remoteReducerWeight')
                    .intProperty('preferLocalReducerThresholdWeight');

                break;

            case 'Custom':
                const clsName = _.get(hadoop, 'mapReducePlanner.Custom.className');

                if (clsName)
                    plannerBean = new EmptyBean(clsName);

                break;

            default:
                // No-op.
        }

        if (plannerBean)
            hadoopBean.beanProperty('mapReducePlanner', plannerBean);

        hadoopBean.longProperty('finishedJobInfoTtl')
            .intProperty('maxParallelTasks')
            .intProperty('maxTaskQueueSize')
            .arrayProperty('nativeLibraryNames', 'nativeLibraryNames', _.get(hadoop, 'nativeLibraryNames'));

        if (!hadoopBean.isEmpty())
            cfg.beanProperty('hadoopConfiguration', hadoopBean);

        return cfg;
    }

    // Generate load balancing configuration group.
    static clusterLoadBalancing(cluster, cfg = this.igniteConfigurationBean(cluster)) {
        const spis = [];

        _.forEach(cluster.loadBalancingSpi, (spi) => {
            let loadBalancingSpi;

            switch (_.get(spi, 'kind')) {
                case 'RoundRobin':
                    loadBalancingSpi = new Bean('org.apache.ignite.spi.loadbalancing.roundrobin.RoundRobinLoadBalancingSpi', 'loadBalancingSpiRR', spi.RoundRobin, clusterDflts.loadBalancingSpi.RoundRobin);

                    loadBalancingSpi.boolProperty('perTask');

                    break;
                case 'Adaptive':
                    loadBalancingSpi = new Bean('org.apache.ignite.spi.loadbalancing.adaptive.AdaptiveLoadBalancingSpi', 'loadBalancingSpiAdaptive', spi.Adaptive);

                    let probeBean;

                    switch (_.get(spi, 'Adaptive.loadProbe.kind')) {
                        case 'Job':
                            probeBean = new Bean('org.apache.ignite.spi.loadbalancing.adaptive.AdaptiveJobCountLoadProbe', 'jobProbe', spi.Adaptive.loadProbe.Job, clusterDflts.loadBalancingSpi.Adaptive.loadProbe.Job);

                            probeBean.boolProperty('useAverage');

                            break;
                        case 'CPU':
                            probeBean = new Bean('org.apache.ignite.spi.loadbalancing.adaptive.AdaptiveCpuLoadProbe', 'cpuProbe', spi.Adaptive.loadProbe.CPU, clusterDflts.loadBalancingSpi.Adaptive.loadProbe.CPU);

                            probeBean.boolProperty('useAverage')
                                .boolProperty('useProcessors')
                                .intProperty('processorCoefficient');

                            break;
                        case 'ProcessingTime':
                            probeBean = new Bean('org.apache.ignite.spi.loadbalancing.adaptive.AdaptiveProcessingTimeLoadProbe', 'timeProbe', spi.Adaptive.loadProbe.ProcessingTime, clusterDflts.loadBalancingSpi.Adaptive.loadProbe.ProcessingTime);

                            probeBean.boolProperty('useAverage');

                            break;
                        case 'Custom':
                            const className = _.get(spi, 'Adaptive.loadProbe.Custom.className');

                            if (className)
                                probeBean = new Bean(className, 'probe', spi.Adaptive.loadProbe.Job.Custom);

                            break;
                        default:
                            // No-op.
                    }

                    if (probeBean)
                        loadBalancingSpi.beanProperty('loadProbe', probeBean);

                    break;
                case 'WeightedRandom':
                    loadBalancingSpi = new Bean('org.apache.ignite.spi.loadbalancing.weightedrandom.WeightedRandomLoadBalancingSpi', 'loadBalancingSpiRandom', spi.WeightedRandom, clusterDflts.loadBalancingSpi.WeightedRandom);

                    loadBalancingSpi.intProperty('nodeWeight')
                        .boolProperty('useWeights');

                    break;
                case 'Custom':
                    const className = _.get(spi, 'Custom.className');

                    if (className)
                        loadBalancingSpi = new Bean(className, 'loadBalancingSpiCustom', spi.Custom);

                    break;
                default:
                    // No-op.
            }

            if (loadBalancingSpi)
                spis.push(loadBalancingSpi);
        });

        if (spis.length)
            cfg.varArgProperty('loadBalancingSpi', 'loadBalancingSpi', spis, 'org.apache.ignite.spi.loadbalancing.LoadBalancingSpi');

        return cfg;
    }

    // Generate logger group.
    static clusterLogger(logger, cfg = this.igniteConfigurationBean()) {
        let loggerBean;

        switch (_.get(logger, 'kind')) {
            case 'Log4j':
                if (logger.Log4j && (logger.Log4j.mode === 'Default' || logger.Log4j.mode === 'Path' && nonEmpty(logger.Log4j.path))) {
                    loggerBean = new Bean('org.apache.ignite.logger.log4j.Log4JLogger',
                        'logger', logger.Log4j, clusterDflts.logger.Log4j);

                    if (loggerBean.valueOf('mode') === 'Path')
                        loggerBean.pathConstructorArgument('path');

                    loggerBean.enumProperty('level');
                }

                break;
            case 'Log4j2':
                if (logger.Log4j2 && nonEmpty(logger.Log4j2.path)) {
                    loggerBean = new Bean('org.apache.ignite.logger.log4j2.Log4J2Logger',
                        'logger', logger.Log4j2, clusterDflts.logger.Log4j2);

                    loggerBean.pathConstructorArgument('path')
                        .enumProperty('level');
                }

                break;
            case 'Null':
                loggerBean = new EmptyBean('org.apache.ignite.logger.NullLogger');

                break;
            case 'Java':
                loggerBean = new EmptyBean('org.apache.ignite.logger.java.JavaLogger');

                break;
            case 'JCL':
                loggerBean = new EmptyBean('org.apache.ignite.logger.jcl.JclLogger');

                break;
            case 'SLF4J':
                loggerBean = new EmptyBean('org.apache.ignite.logger.slf4j.Slf4jLogger');

                break;
            case 'Custom':
                if (logger.Custom && nonEmpty(logger.Custom.class))
                    loggerBean = new EmptyBean(logger.Custom.class);

                break;
            default:
                return cfg;
        }

        if (loggerBean)
            cfg.beanProperty('gridLogger', loggerBean);

        return cfg;
    }

    // Generate memory configuration.
    static clusterMemory(memoryConfiguration, available, cfg = this.igniteConfigurationBean()) {
        const memoryBean = new Bean('org.apache.ignite.configuration.MemoryConfiguration', 'memoryConfiguration', memoryConfiguration, clusterDflts.memoryConfiguration);

        memoryBean.intProperty('pageSize')
            .intProperty('concurrencyLevel')
            .longProperty('systemCacheInitialSize')
            .longProperty('systemCacheMaxSize')
            .stringProperty('defaultMemoryPolicyName');

        if (memoryBean.valueOf('defaultMemoryPolicyName') === 'default')
            memoryBean.longProperty('defaultMemoryPolicySize');

        const policies = [];

        _.forEach(_.get(memoryConfiguration, 'memoryPolicies'), (plc) => {
            const plcBean = new Bean('org.apache.ignite.configuration.MemoryPolicyConfiguration', 'policy', plc, clusterDflts.memoryConfiguration.memoryPolicies);

            plcBean.stringProperty('name')
                .longProperty('initialSize')
                .longProperty('maxSize')
                .stringProperty('swapFilePath')
                .enumProperty('pageEvictionMode')
                .doubleProperty('evictionThreshold')
                .intProperty('emptyPagesPoolSize')
                .boolProperty('metricsEnabled');

            if (available('2.1.0')) {
                plcBean.intProperty('subIntervals')
                    .longProperty('rateTimeInterval');
            }

            if (plcBean.isEmpty()) return;

            policies.push(plcBean);
        });

        if (!_.isEmpty(policies))
            memoryBean.varArgProperty('memoryPolicies', 'memoryPolicies', policies, 'org.apache.ignite.configuration.MemoryPolicyConfiguration');

        if (memoryBean.isEmpty())
            return cfg;

        cfg.beanProperty('memoryConfiguration', memoryBean);

        return cfg;
    }

    static dataRegionConfiguration(dataRegionCfg) {
        const plcBean = new Bean('org.apache.ignite.configuration.DataRegionConfiguration', 'dataRegionCfg', dataRegionCfg, clusterDflts.dataStorageConfiguration.dataRegionConfigurations);

        plcBean.stringProperty('name')
            .longProperty('initialSize')
            .longProperty('maxSize')
            .stringProperty('swapPath')
            .enumProperty('pageEvictionMode')
            .doubleProperty('evictionThreshold')
            .intProperty('emptyPagesPoolSize')
            .intProperty('metricsSubIntervalCount')
            .longProperty('metricsRateTimeInterval')
            .longProperty('checkpointPageBufferSize')
            .boolProperty('metricsEnabled')
            .boolProperty('persistenceEnabled');

        return plcBean;
    }

    // Generate data storage configuration.
    static clusterDataStorageConfiguration(dataStorageCfg, available, cfg = this.igniteConfigurationBean()) {
        if (!available('2.3.0'))
            return cfg;

        const storageBean = new Bean('org.apache.ignite.configuration.DataStorageConfiguration', 'dataStorageCfg', dataStorageCfg, clusterDflts.dataStorageConfiguration);

        storageBean.intProperty('pageSize')
            .intProperty('concurrencyLevel')
            .longProperty('systemRegionInitialSize')
            .longProperty('systemRegionMaxSize');

        const dfltDataRegionCfg = this.dataRegionConfiguration(_.get(dataStorageCfg, 'defaultDataRegionConfiguration'));

        if (!dfltDataRegionCfg.isEmpty())
            storageBean.beanProperty('defaultDataRegionConfiguration', dfltDataRegionCfg);

        const dataRegionCfgs = [];

        _.forEach(_.get(dataStorageCfg, 'dataRegionConfigurations'), (dataRegionCfg) => {
            const plcBean = this.dataRegionConfiguration(dataRegionCfg);

            if (plcBean.isEmpty())
                return;

            dataRegionCfgs.push(plcBean);
        });

        if (!_.isEmpty(dataRegionCfgs))
            storageBean.varArgProperty('dataRegionConfigurations', 'dataRegionConfigurations', dataRegionCfgs, 'org.apache.ignite.configuration.DataRegionConfiguration');

        storageBean.stringProperty('storagePath')
            .longProperty('checkpointFrequency')
            .intProperty('checkpointThreads')
            .enumProperty('checkpointWriteOrder')
            .enumProperty('walMode')
            .stringProperty('walPath')
            .stringProperty('walArchivePath')
            .intProperty('walSegments')
            .intProperty('walSegmentSize')
            .intProperty('walHistorySize');

        if (available('2.4.0'))
            storageBean.intProperty('walBufferSize');

        storageBean.longProperty('walFlushFrequency')
            .longProperty('walFsyncDelayNanos')
            .intProperty('walRecordIteratorBufferSize')
            .longProperty('lockWaitTime')
            .intProperty('walThreadLocalBufferSize')
            .intProperty('metricsSubIntervalCount')
            .longProperty('metricsRateTimeInterval')
            .longProperty('walAutoArchiveAfterInactivity')
            .boolProperty('metricsEnabled')
            .boolProperty('alwaysWriteFullPages')
            .boolProperty('writeThrottlingEnabled');

        if (available('2.4.0'))
            storageBean.boolProperty('walCompactionEnabled');

        const fileIOFactory = _.get(dataStorageCfg, 'fileIOFactory');

        let factoryBean;

        if (fileIOFactory === 'RANDOM')
            factoryBean = new Bean('org.apache.ignite.internal.processors.cache.persistence.file.RandomAccessFileIOFactory', 'rndFileIoFactory', {});
        else if (fileIOFactory === 'ASYNC')
            factoryBean = new Bean('org.apache.ignite.internal.processors.cache.persistence.file.AsyncFileIOFactory', 'asyncFileIoFactory', {});

        if (factoryBean)
            storageBean.beanProperty('fileIOFactory', factoryBean);

        if (storageBean.isEmpty())
            return cfg;

        cfg.beanProperty('dataStorageConfiguration', storageBean);

        return cfg;
    }

    // Generate miscellaneous configuration.
    static clusterMisc(cluster, available, cfg = this.igniteConfigurationBean(cluster)) {
        cfg.stringProperty('workDirectory');

        // Since Ignite 2.0
        if (available('2.0.0')) {
            cfg.stringProperty('consistentId')
                .emptyBeanProperty('warmupClosure')
                .boolProperty('activeOnStart')
                .boolProperty('cacheSanityCheckEnabled');
        }

        if (available(['1.0.0', '2.1.0']))
            cfg.boolProperty('lateAffinityAssignment');

        return cfg;
    }

    // Generate IGFSs configs.
    static clusterIgfss(igfss, available, cfg = this.igniteConfigurationBean()) {
        const igfsCfgs = _.map(igfss, (igfs) => {
            const igfsCfg = this.igfsGeneral(igfs, available);

            this.igfsIPC(igfs, igfsCfg);
            this.igfsFragmentizer(igfs, igfsCfg);

            // Removed in ignite 2.0
            if (available(['1.0.0', '2.0.0']))
                this.igfsDualMode(igfs, igfsCfg);

            this.igfsSecondFS(igfs, igfsCfg);
            this.igfsMisc(igfs, available, igfsCfg);

            return igfsCfg;
        });

        cfg.varArgProperty('igfsCfgs', 'fileSystemConfiguration', igfsCfgs, 'org.apache.ignite.configuration.FileSystemConfiguration');

        return cfg;
    }

    // Generate marshaller group.
    static clusterMarshaller(cluster, available, cfg = this.igniteConfigurationBean(cluster)) {
        if (available(['1.0.0', '2.1.0'])) {
            const kind = _.get(cluster.marshaller, 'kind');
            const settings = _.get(cluster.marshaller, kind);

            let bean;

            switch (kind) {
                case 'OptimizedMarshaller':
                    if (available(['1.0.0', '2.0.0'])) {
                        bean = new Bean('org.apache.ignite.marshaller.optimized.OptimizedMarshaller', 'marshaller', settings)
                            .intProperty('poolSize')
                            .intProperty('requireSerializable');
                    }

                    break;

                case 'JdkMarshaller':
                    bean = new Bean('org.apache.ignite.marshaller.jdk.JdkMarshaller', 'marshaller', settings);

                    break;

                default:
                // No-op.
            }

            if (bean)
                cfg.beanProperty('marshaller', bean);
        }

        cfg.intProperty('marshalLocalJobs');

        // Removed in ignite 2.0
        if (available(['1.0.0', '2.0.0'])) {
            cfg.intProperty('marshallerCacheKeepAliveTime')
                .intProperty('marshallerCacheThreadPoolSize', 'marshallerCachePoolSize');
        }

        return cfg;
    }

    // Generate metrics group.
    static clusterMetrics(cluster, available, cfg = this.igniteConfigurationBean(cluster)) {
        cfg.longProperty('metricsExpireTime')
            .intProperty('metricsHistorySize')
            .longProperty('metricsLogFrequency');

        // Since ignite 2.0
        if (available('2.0.0'))
            cfg.longProperty('metricsUpdateFrequency');

        return cfg;
    }

    // Generate ODBC group.
    static clusterODBC(odbc, available, cfg = this.igniteConfigurationBean()) {
        //  Deprecated in ignite 2.1
        if (available('2.1.0') || _.get(odbc, 'odbcEnabled') !== true)
            return cfg;

        const bean = new Bean('org.apache.ignite.configuration.OdbcConfiguration', 'odbcConfiguration',
            odbc, clusterDflts.odbcConfiguration);

        bean.stringProperty('endpointAddress')
            .intProperty('socketSendBufferSize')
            .intProperty('socketReceiveBufferSize')
            .intProperty('maxOpenCursors')
            .intProperty('threadPoolSize');

        cfg.beanProperty('odbcConfiguration', bean);

        return cfg;
    }

    // Generate cluster query group.
    static clusterQuery(cluster, available, cfg = this.igniteConfigurationBean(cluster)) {
        if (!available('2.1.0'))
            return cfg;

        cfg.longProperty('longQueryWarningTimeout');

        if (_.get(cluster, 'sqlConnectorConfiguration.enabled') !== true)
            return cfg;

        const bean = new Bean('org.apache.ignite.configuration.SqlConnectorConfiguration', 'sqlConnCfg',
            cluster.sqlConnectorConfiguration, clusterDflts.sqlConnectorConfiguration);

        bean.stringProperty('host')
            .intProperty('port')
            .intProperty('portRange')
            .intProperty('socketSendBufferSize')
            .intProperty('socketReceiveBufferSize')
            .intProperty('maxOpenCursorsPerConnection')
            .intProperty('threadPoolSize')
            .boolProperty('tcpNoDelay');

        cfg.beanProperty('sqlConnectorConfiguration', bean);

        return cfg;
    }

    // Generate cluster query group.
    static clusterPersistence(persistence, available, cfg = this.igniteConfigurationBean()) {
        if (!available(['2.1.0', '2.3.0']) || _.get(persistence, 'enabled') !== true)
            return cfg;

        const bean = new Bean('org.apache.ignite.configuration.PersistentStoreConfiguration', 'PersistenceCfg',
            persistence, clusterDflts.persistenceStoreConfiguration);

        bean.stringProperty('persistentStorePath')
            .boolProperty('metricsEnabled')
            .boolProperty('alwaysWriteFullPages')
            .longProperty('checkpointingFrequency')
            .longProperty('checkpointingPageBufferSize')
            .intProperty('checkpointingThreads')
            .stringProperty('walStorePath')
            .stringProperty('walArchivePath')
            .intProperty('walSegments')
            .intProperty('walSegmentSize')
            .intProperty('walHistorySize')
            .longProperty('walFlushFrequency')
            .longProperty('walFsyncDelayNanos')
            .intProperty('walRecordIteratorBufferSize')
            .longProperty('lockWaitTime')
            .longProperty('rateTimeInterval')
            .intProperty('tlbSize')
            .intProperty('subIntervals');

        cfg.beanProperty('persistentStoreConfiguration', bean);

        return cfg;
    }

    // Java code generator for cluster's service configurations.
    static clusterServiceConfiguration(srvs, caches, cfg = this.igniteConfigurationBean()) {
        const srvBeans = [];

        _.forEach(srvs, (srv) => {
            const bean = new Bean('org.apache.ignite.services.ServiceConfiguration', 'service', srv, clusterDflts.serviceConfigurations);

            bean.stringProperty('name')
                .emptyBeanProperty('service')
                .intProperty('maxPerNodeCount')
                .intProperty('totalCount')
                .stringProperty('cache', 'cacheName', (_id) => _id ? _.find(caches, {_id}).name : null)
                .stringProperty('affinityKey');

            srvBeans.push(bean);
        });

        if (!_.isEmpty(srvBeans))
            cfg.arrayProperty('services', 'serviceConfiguration', srvBeans, 'org.apache.ignite.services.ServiceConfiguration');

        return cfg;
    }

    // Java code generator for cluster's SSL configuration.
    static clusterSsl(cluster, cfg = this.igniteConfigurationBean(cluster)) {
        if (cluster.sslEnabled && nonNil(cluster.sslContextFactory)) {
            const bean = new Bean('org.apache.ignite.ssl.SslContextFactory', 'sslCtxFactory',
                cluster.sslContextFactory);

            bean.intProperty('keyAlgorithm')
                .pathProperty('keyStoreFilePath');

            if (nonEmpty(bean.valueOf('keyStoreFilePath')))
                bean.propertyChar('keyStorePassword', 'ssl.key.storage.password', 'YOUR_SSL_KEY_STORAGE_PASSWORD');

            bean.intProperty('keyStoreType')
                .intProperty('protocol');

            if (nonEmpty(cluster.sslContextFactory.trustManagers)) {
                bean.arrayProperty('trustManagers', 'trustManagers',
                    _.map(cluster.sslContextFactory.trustManagers, (clsName) => new EmptyBean(clsName)),
                    'javax.net.ssl.TrustManager');
            }
            else {
                bean.pathProperty('trustStoreFilePath');

                if (nonEmpty(bean.valueOf('trustStoreFilePath')))
                    bean.propertyChar('trustStorePassword', 'ssl.trust.storage.password', 'YOUR_SSL_TRUST_STORAGE_PASSWORD');

                bean.intProperty('trustStoreType');
            }

            cfg.beanProperty('sslContextFactory', bean);
        }

        return cfg;
    }

    // Generate swap group.
    static clusterSwap(cluster, cfg = this.igniteConfigurationBean(cluster)) {
        if (_.get(cluster.swapSpaceSpi, 'kind') === 'FileSwapSpaceSpi') {
            const bean = new Bean('org.apache.ignite.spi.swapspace.file.FileSwapSpaceSpi', 'swapSpaceSpi',
                cluster.swapSpaceSpi.FileSwapSpaceSpi);

            bean.pathProperty('baseDirectory')
                .intProperty('readStripesNumber')
                .floatProperty('maximumSparsity')
                .intProperty('maxWriteQueueSize')
                .intProperty('writeBufferSize');

            cfg.beanProperty('swapSpaceSpi', bean);
        }

        return cfg;
    }

    // Generate time group.
    static clusterTime(cluster, available, cfg = this.igniteConfigurationBean(cluster)) {
        if (available(['1.0.0', '2.0.0'])) {
            cfg.intProperty('clockSyncSamples')
                .intProperty('clockSyncFrequency');
        }

        cfg.intProperty('timeServerPortBase')
            .intProperty('timeServerPortRange');

        return cfg;
    }

    // Generate thread pools group.
    static clusterPools(cluster, available, cfg = this.igniteConfigurationBean(cluster)) {
        cfg.intProperty('publicThreadPoolSize')
            .intProperty('systemThreadPoolSize')
            .intProperty('serviceThreadPoolSize')
            .intProperty('managementThreadPoolSize')
            .intProperty('igfsThreadPoolSize')
            .intProperty('rebalanceThreadPoolSize')
            .intProperty('utilityCacheThreadPoolSize', 'utilityCachePoolSize')
            .longProperty('utilityCacheKeepAliveTime')
            .intProperty('asyncCallbackPoolSize')
            .intProperty('stripedPoolSize');

        // Since ignite 2.0
        if (available('2.0.0')) {
            cfg.intProperty('dataStreamerThreadPoolSize')
                .intProperty('queryThreadPoolSize');

            const executors = [];

            _.forEach(cluster.executorConfiguration, (exec) => {
                const execBean = new Bean('org.apache.ignite.configuration.ExecutorConfiguration', 'executor', exec);

                execBean.stringProperty('name')
                    .intProperty('size');

                if (!execBean.isEmpty())
                    executors.push(execBean);
            });

            if (!_.isEmpty(executors))
                cfg.arrayProperty('executors', 'executorConfiguration', executors, 'org.apache.ignite.configuration.ExecutorConfiguration');
        }

        return cfg;
    }

    // Generate transactions group.
    static clusterTransactions(transactionConfiguration, cfg = this.igniteConfigurationBean()) {
        const bean = new Bean('org.apache.ignite.configuration.TransactionConfiguration', 'transactionConfiguration',
            transactionConfiguration, clusterDflts.transactionConfiguration);

        bean.enumProperty('defaultTxConcurrency')
            .enumProperty('defaultTxIsolation')
            .longProperty('defaultTxTimeout')
            .intProperty('pessimisticTxLogLinger')
            .intProperty('pessimisticTxLogSize')
            .boolProperty('txSerializableEnabled')
            .emptyBeanProperty('txManagerFactory');

        if (bean.nonEmpty())
            cfg.beanProperty('transactionConfiguration', bean);

        return cfg;
    }

    // Generate user attributes group.
    static clusterUserAttributes(cluster, cfg = this.igniteConfigurationBean(cluster)) {
        cfg.mapProperty('attrs', 'attributes', 'userAttributes');

        return cfg;
    }

    // Generate domain model for general group.
    static domainModelGeneral(domain, cfg = this.domainConfigurationBean(domain)) {
        switch (cfg.valueOf('queryMetadata')) {
            case 'Annotations':
                if (nonNil(domain.keyType) && nonNil(domain.valueType)) {
                    cfg.varArgProperty('indexedTypes', 'indexedTypes',
                        [javaTypes.fullClassName(domain.keyType), javaTypes.fullClassName(domain.valueType)],
                        'java.lang.Class');
                }

                break;
            case 'Configuration':
                cfg.stringProperty('keyType', 'keyType', (val) => javaTypes.fullClassName(val))
                    .stringProperty('valueType', 'valueType', (val) => javaTypes.fullClassName(val));

                break;
            default:
        }

        return cfg;
    }

    // Generate domain model for query group.
    static domainModelQuery(domain, available, cfg = this.domainConfigurationBean(domain)) {
        if (cfg.valueOf('queryMetadata') === 'Configuration') {
            const fields = _.filter(_.map(domain.fields,
                (e) => ({name: e.name, className: javaTypes.stringClassName(e.className)})), (field) => {
                return field.name !== domain.keyFieldName && field.name !== domain.valueFieldName;
            });

            cfg.stringProperty('tableName');

            if (available('2.0.0')) {
                cfg.stringProperty('keyFieldName')
                    .stringProperty('valueFieldName');

                const keyFieldName = cfg.valueOf('keyFieldName');
                const valFieldName = cfg.valueOf('valueFieldName');

                if (keyFieldName)
                    fields.push({name: keyFieldName, className: javaTypes.stringClassName(domain.keyType)});

                if (valFieldName)
                    fields.push({name: valFieldName, className: javaTypes.stringClassName(domain.valueType)});
            }

            cfg.collectionProperty('keyFields', 'keyFields', domain.queryKeyFields, 'java.lang.String', 'java.util.HashSet')
                .mapProperty('fields', fields, 'fields', true)
                .mapProperty('aliases', 'aliases');

            const indexes = _.map(domain.indexes, (index) =>
                new Bean('org.apache.ignite.cache.QueryIndex', 'index', index, cacheDflts.indexes)
                    .stringProperty('name')
                    .enumProperty('indexType')
                    .mapProperty('indFlds', 'fields', 'fields', true)
            );

            cfg.collectionProperty('indexes', 'indexes', indexes, 'org.apache.ignite.cache.QueryIndex');
        }

        return cfg;
    }

    // Generate domain model db fields.
    static _domainModelDatabaseFields(cfg, propName, domain) {
        const fields = _.map(domain[propName], (field) => {
            return new Bean('org.apache.ignite.cache.store.jdbc.JdbcTypeField', 'typeField', field, cacheDflts.typeField)
                .constantConstructorArgument('databaseFieldType')
                .stringConstructorArgument('databaseFieldName')
                .classConstructorArgument('javaFieldType')
                .stringConstructorArgument('javaFieldName');
        });

        cfg.varArgProperty(propName, propName, fields, 'org.apache.ignite.cache.store.jdbc.JdbcTypeField');

        return cfg;
    }

    // Generate domain model for store group.
    static domainStore(domain, cfg = this.domainJdbcTypeBean(domain)) {
        cfg.stringProperty('databaseSchema')
            .stringProperty('databaseTable');

        this._domainModelDatabaseFields(cfg, 'keyFields', domain);
        this._domainModelDatabaseFields(cfg, 'valueFields', domain);

        return cfg;
    }

    /**
     * Generate eviction policy object.
     * @param {Object} ccfg Parent configuration.
     * @param {Function} available Function to check feature is supported in Ignite current version.
     * @param {Boolean} near Near cache flag.
     * @param {Object} src Source.
     * @param {Object} dflt Default.
     * @returns {Object} Parent configuration.
     * @private
     */
    static _evictionPolicy(ccfg, available, near, src, dflt) {
        let propName;
        let beanProps;

        if (available('2.4.0')) {
            switch (_.get(src, 'kind')) {
                case 'LRU': beanProps = {cls: 'org.apache.ignite.cache.eviction.lru.LruEvictionPolicyFactory', src: src.LRU };
                    break;

                case 'FIFO': beanProps = {cls: 'org.apache.ignite.cache.eviction.fifo.FifoEvictionPolicyFactory', src: src.FIFO };
                    break;

                case 'SORTED': beanProps = {cls: 'org.apache.ignite.cache.eviction.sorted.SortedEvictionPolicyFactory', src: src.SORTED };
                    break;

                default:
                    return ccfg;
            }

            propName = (near ? 'nearEviction' : 'eviction') + 'PolicyFactory';
        }
        else {
            switch (_.get(src, 'kind')) {
                case 'LRU': beanProps = {cls: 'org.apache.ignite.cache.eviction.lru.LruEvictionPolicy', src: src.LRU };
                    break;

                case 'FIFO': beanProps = {cls: 'org.apache.ignite.cache.eviction.fifo.FifoEvictionPolicy', src: src.FIFO };
                    break;

                case 'SORTED': beanProps = {cls: 'org.apache.ignite.cache.eviction.sorted.SortedEvictionPolicy', src: src.SORTED };
                    break;

                default:
                    return ccfg;
            }

            propName = (near ? 'nearEviction' : 'eviction') + 'Policy';
        }

        const bean = new Bean(beanProps.cls, propName, beanProps.src, dflt);

        bean.intProperty('batchSize')
            .intProperty('maxMemorySize')
            .intProperty('maxSize');

        ccfg.beanProperty(propName, bean);

        return ccfg;
    }

    // Generate cache general group.
    static cacheGeneral(cache, available, ccfg = this.cacheConfigurationBean(cache)) {
        ccfg.stringProperty('name');

        if (available('2.1.0'))
            ccfg.stringProperty('groupName');

        ccfg.enumProperty('cacheMode')
            .enumProperty('atomicityMode');

        if (ccfg.valueOf('cacheMode') === 'PARTITIONED' && ccfg.valueOf('backups')) {
            ccfg.intProperty('backups')
                .intProperty('readFromBackup');
        }

        // Since ignite 2.0
        if (available('2.0.0'))
            ccfg.enumProperty('partitionLossPolicy');

        ccfg.intProperty('copyOnRead');

        if (ccfg.valueOf('cacheMode') === 'PARTITIONED' && ccfg.valueOf('atomicityMode') === 'TRANSACTIONAL')
            ccfg.intProperty('isInvalidate', 'invalidate');

        return ccfg;
    }

    // Generation of constructor for affinity function.
    static affinityFunction(cls, func) {
        const affBean = new Bean(cls, 'affinityFunction', func);

        affBean.boolConstructorArgument('excludeNeighbors')
            .intProperty('partitions')
            .emptyBeanProperty('affinityBackupFilter');

        return affBean;
    }

    // Generate affinity function.
    static affinity(affinity, cfg) {
        switch (_.get(affinity, 'kind')) {
            case 'Rendezvous':
                cfg.beanProperty('affinity', this.affinityFunction('org.apache.ignite.cache.affinity.rendezvous.RendezvousAffinityFunction', affinity.Rendezvous));

                break;
            case 'Fair':
                cfg.beanProperty('affinity', this.affinityFunction('org.apache.ignite.cache.affinity.fair.FairAffinityFunction', affinity.Fair));

                break;
            case 'Custom':
                cfg.emptyBeanProperty('affinity.Custom.className', 'affinity');

                break;
            default:
            // No-op.
        }
    }

    // Generate cache memory group.
    static cacheAffinity(cache, available, ccfg = this.cacheConfigurationBean(cache)) {
        this.affinity(cache.affinity, ccfg);

        ccfg.emptyBeanProperty('affinityMapper');

        // Since ignite 2.0
        if (available('2.0.0'))
            ccfg.emptyBeanProperty('topologyValidator');

        return ccfg;
    }

    // Generate cache memory group.
    static cacheMemory(cache, available, ccfg = this.cacheConfigurationBean(cache)) {
        // Since ignite 2.0
        if (available(['2.0.0', '2.3.0']))
            ccfg.stringProperty('memoryPolicyName');

        if (available('2.3.0'))
            ccfg.stringProperty('dataRegionName');

        // Removed in ignite 2.0
        if (available(['1.0.0', '2.0.0'])) {
            ccfg.enumProperty('memoryMode');

            if (ccfg.valueOf('memoryMode') !== 'OFFHEAP_VALUES')
                ccfg.longProperty('offHeapMaxMemory');
        }

        // Since ignite 2.0
        if (available('2.0.0')) {
            ccfg.boolProperty('onheapCacheEnabled')
                .emptyBeanProperty('evictionFilter');
        }

        this._evictionPolicy(ccfg, available, false, cache.evictionPolicy, cacheDflts.evictionPolicy);

        // Removed in ignite 2.0
        if (available(['1.0.0', '2.0.0'])) {
            ccfg.intProperty('startSize')
                .boolProperty('swapEnabled');
        }

        return ccfg;
    }

    // Generate cache queries & Indexing group.
    static cacheQuery(cache, domains, available, ccfg = this.cacheConfigurationBean(cache)) {
        const indexedTypes = _.reduce(domains, (acc, domain) => {
            if (domain.queryMetadata === 'Annotations')
                acc.push(javaTypes.fullClassName(domain.keyType), javaTypes.fullClassName(domain.valueType));

            return acc;
        }, []);

        ccfg.stringProperty('sqlSchema');

        // Removed in ignite 2.0
        if (available(['1.0.0', '2.0.0']))
            ccfg.intProperty('sqlOnheapRowCacheSize');

        ccfg.longProperty('longQueryWarningTimeout')
            .arrayProperty('indexedTypes', 'indexedTypes', indexedTypes, 'java.lang.Class')
            .intProperty('queryDetailMetricsSize')
            .arrayProperty('sqlFunctionClasses', 'sqlFunctionClasses', cache.sqlFunctionClasses, 'java.lang.Class');

        // Removed in ignite 2.0
        if (available(['1.0.0', '2.0.0']))
            ccfg.intProperty('snapshotableIndex');

        ccfg.intProperty('sqlEscapeAll');

        // Since ignite 2.0
        if (available('2.0.0')) {
            ccfg.intProperty('queryParallelism')
                .intProperty('sqlIndexMaxInlineSize');
        }

        return ccfg;
    }

    // Generate cache store group.
    static cacheStore(cache, domains, available, ccfg = this.cacheConfigurationBean(cache)) {
        const kind = _.get(cache, 'cacheStoreFactory.kind');

        if (kind && cache.cacheStoreFactory[kind]) {
            let bean = null;

            const storeFactory = cache.cacheStoreFactory[kind];

            switch (kind) {
                case 'CacheJdbcPojoStoreFactory':
                    bean = new Bean('org.apache.ignite.cache.store.jdbc.CacheJdbcPojoStoreFactory', 'cacheStoreFactory',
                        storeFactory, cacheDflts.cacheStoreFactory.CacheJdbcPojoStoreFactory);

                    const jdbcId = bean.valueOf('dataSourceBean');

                    bean.dataSource(jdbcId, 'dataSourceBean', this.dataSourceBean(jdbcId, storeFactory.dialect))
                        .beanProperty('dialect', new EmptyBean(this.dialectClsName(storeFactory.dialect)));

                    bean.intProperty('batchSize')
                        .intProperty('maximumPoolSize')
                        .intProperty('maximumWriteAttempts')
                        .intProperty('parallelLoadCacheMinimumThreshold')
                        .emptyBeanProperty('hasher')
                        .emptyBeanProperty('transformer')
                        .boolProperty('sqlEscapeAll');

                    const setType = (typeBean, propName) => {
                        if (javaTypes.nonBuiltInClass(typeBean.valueOf(propName)))
                            typeBean.stringProperty(propName);
                        else
                            typeBean.classProperty(propName);
                    };

                    const types = _.reduce(domains, (acc, domain) => {
                        if (isNil(domain.databaseTable))
                            return acc;

                        const typeBean = this.domainJdbcTypeBean(_.merge({}, domain, {cacheName: cache.name}))
                            .stringProperty('cacheName');

                        setType(typeBean, 'keyType');
                        setType(typeBean, 'valueType');

                        this.domainStore(domain, typeBean);

                        acc.push(typeBean);

                        return acc;
                    }, []);

                    bean.varArgProperty('types', 'types', types, 'org.apache.ignite.cache.store.jdbc.JdbcType');

                    break;
                case 'CacheJdbcBlobStoreFactory':
                    bean = new Bean('org.apache.ignite.cache.store.jdbc.CacheJdbcBlobStoreFactory', 'cacheStoreFactory',
                        storeFactory);

                    if (bean.valueOf('connectVia') === 'DataSource') {
                        const blobId = bean.valueOf('dataSourceBean');

                        bean.dataSource(blobId, 'dataSourceBean', this.dataSourceBean(blobId, storeFactory.dialect));
                    }
                    else {
                        ccfg.stringProperty('connectionUrl')
                            .stringProperty('user')
                            .property('password', `ds.${storeFactory.user}.password`, 'YOUR_PASSWORD');
                    }

                    bean.boolProperty('initSchema')
                        .stringProperty('createTableQuery')
                        .stringProperty('loadQuery')
                        .stringProperty('insertQuery')
                        .stringProperty('updateQuery')
                        .stringProperty('deleteQuery');

                    break;
                case 'CacheHibernateBlobStoreFactory':
                    bean = new Bean('org.apache.ignite.cache.store.hibernate.CacheHibernateBlobStoreFactory',
                        'cacheStoreFactory', storeFactory);

                    bean.propsProperty('props', 'hibernateProperties');

                    break;
                default:
            }

            if (bean)
                ccfg.beanProperty('cacheStoreFactory', bean);
        }

        ccfg.boolProperty('storeKeepBinary')
            .boolProperty('loadPreviousValue')
            .boolProperty('readThrough')
            .boolProperty('writeThrough');

        if (ccfg.valueOf('writeBehindEnabled')) {
            ccfg.boolProperty('writeBehindEnabled')
                .intProperty('writeBehindBatchSize')
                .intProperty('writeBehindFlushSize')
                .longProperty('writeBehindFlushFrequency')
                .intProperty('writeBehindFlushThreadCount');

            // Since ignite 2.0
            if (available('2.0.0'))
                ccfg.boolProperty('writeBehindCoalescing');
        }

        return ccfg;
    }

    // Generate cache concurrency control group.
    static cacheConcurrency(cache, available, ccfg = this.cacheConfigurationBean(cache)) {
        ccfg.intProperty('maxConcurrentAsyncOperations')
            .longProperty('defaultLockTimeout');

        // Removed in ignite 2.0
        if (available(['1.0.0', '2.0.0']))
            ccfg.enumProperty('atomicWriteOrderMode');

        ccfg.enumProperty('writeSynchronizationMode');

        return ccfg;
    }

    static nodeFilter(filter, igfss) {
        const kind = _.get(filter, 'kind');

        const settings = _.get(filter, kind);

        if (!isNil(settings)) {
            switch (kind) {
                case 'IGFS':
                    const foundIgfs = _.find(igfss, {_id: settings.igfs});

                    if (foundIgfs) {
                        return new Bean('org.apache.ignite.internal.processors.igfs.IgfsNodePredicate', 'nodeFilter', foundIgfs)
                            .stringConstructorArgument('name');
                    }

                    break;
                case 'Custom':
                    if (nonEmpty(settings.className))
                        return new EmptyBean(settings.className);

                    break;
                default:
                // No-op.
            }
        }

        return null;
    }

    // Generate cache node filter group.
    static cacheNodeFilter(cache, igfss, ccfg = this.cacheConfigurationBean(cache)) {
        const filter = _.get(cache, 'nodeFilter');

        const filterBean = this.nodeFilter(filter, igfss);

        if (filterBean)
            ccfg.beanProperty('nodeFilter', filterBean);

        return ccfg;
    }

    // Generate cache rebalance group.
    static cacheRebalance(cache, ccfg = this.cacheConfigurationBean(cache)) {
        if (ccfg.valueOf('cacheMode') !== 'LOCAL') {
            ccfg.enumProperty('rebalanceMode')
                .intProperty('rebalanceThreadPoolSize')
                .intProperty('rebalanceBatchSize')
                .longProperty('rebalanceBatchesPrefetchCount')
                .intProperty('rebalanceOrder')
                .longProperty('rebalanceDelay')
                .longProperty('rebalanceTimeout')
                .longProperty('rebalanceThrottle');
        }

        if (ccfg.includes('igfsAffinnityGroupSize')) {
            const bean = new Bean('org.apache.ignite.igfs.IgfsGroupDataBlocksKeyMapper', 'affinityMapper', cache)
                .intConstructorArgument('igfsAffinnityGroupSize');

            ccfg.beanProperty('affinityMapper', bean);
        }

        return ccfg;
    }

    // Generate server near cache group.
    static cacheNearServer(cache, available, ccfg = this.cacheConfigurationBean(cache)) {
        if (ccfg.valueOf('cacheMode') === 'PARTITIONED' && _.get(cache, 'nearConfiguration.enabled')) {
            const bean = new Bean('org.apache.ignite.configuration.NearCacheConfiguration', 'nearConfiguration',
                cache.nearConfiguration, cacheDflts.nearConfiguration);

            bean.intProperty('nearStartSize');

            this._evictionPolicy(bean, available, true, bean.valueOf('nearEvictionPolicy'), cacheDflts.evictionPolicy);

            ccfg.beanProperty('nearConfiguration', bean);
        }

        return ccfg;
    }

    // Generate client near cache group.
    static cacheNearClient(cache, available, ccfg = this.cacheConfigurationBean(cache)) {
        if (ccfg.valueOf('cacheMode') === 'PARTITIONED' && _.get(cache, 'clientNearConfiguration.enabled')) {
            const bean = new Bean('org.apache.ignite.configuration.NearCacheConfiguration',
                javaTypes.toJavaName('nearConfiguration', ccfg.valueOf('name')),
                cache.clientNearConfiguration, cacheDflts.clientNearConfiguration);

            bean.intProperty('nearStartSize');

            this._evictionPolicy(bean, available, true, bean.valueOf('nearEvictionPolicy'), cacheDflts.evictionPolicy);

            return bean;
        }

        return ccfg;
    }

    // Generate cache statistics group.
    static cacheStatistics(cache, ccfg = this.cacheConfigurationBean(cache)) {
        ccfg.boolProperty('statisticsEnabled')
            .boolProperty('managementEnabled');

        return ccfg;
    }

    // Generate domain models configs.
    static cacheDomains(domains, available, ccfg) {
        const qryEntities = _.reduce(domains, (acc, domain) => {
            if (isNil(domain.queryMetadata) || domain.queryMetadata === 'Configuration') {
                const qryEntity = this.domainModelGeneral(domain);

                this.domainModelQuery(domain, available, qryEntity);

                acc.push(qryEntity);
            }

            return acc;
        }, []);

        ccfg.collectionProperty('qryEntities', 'queryEntities', qryEntities, 'org.apache.ignite.cache.QueryEntity');
    }

    static cacheConfiguration(cache, available, ccfg = this.cacheConfigurationBean(cache)) {
        this.cacheGeneral(cache, available, ccfg);
        this.cacheAffinity(cache, available, ccfg);
        this.cacheMemory(cache, available, ccfg);
        this.cacheQuery(cache, cache.domains, available, ccfg);
        this.cacheStore(cache, cache.domains, available, ccfg);

        const igfs = _.get(cache, 'nodeFilter.IGFS.instance');
        this.cacheNodeFilter(cache, igfs ? [igfs] : [], ccfg);
        this.cacheConcurrency(cache, available, ccfg);
        this.cacheRebalance(cache, ccfg);
        this.cacheNearServer(cache, available, ccfg);
        this.cacheStatistics(cache, ccfg);
        this.cacheDomains(cache.domains, available, ccfg);

        return ccfg;
    }

    // Generate IGFS general group.
    static igfsGeneral(igfs, available, cfg = this.igfsConfigurationBean(igfs)) {
        if (_.isEmpty(igfs.name))
            return cfg;

        cfg.stringProperty('name');

        // Removed in ignite 2.0
        if (available(['1.0.0', '2.0.0'])) {
            cfg.stringProperty('name', 'dataCacheName', (name) => name + '-data')
                .stringProperty('name', 'metaCacheName', (name) => name + '-meta');
        }

        cfg.enumProperty('defaultMode');

        return cfg;
    }

    // Generate IGFS secondary file system group.
    static igfsSecondFS(igfs, cfg = this.igfsConfigurationBean(igfs)) {
        if (igfs.secondaryFileSystemEnabled) {
            const secondFs = igfs.secondaryFileSystem || {};

            const bean = new Bean('org.apache.ignite.hadoop.fs.IgniteHadoopIgfsSecondaryFileSystem',
                'secondaryFileSystem', secondFs, igfsDflts.secondaryFileSystem);

            bean.stringProperty('userName', 'defaultUserName');

            const factoryBean = new Bean('org.apache.ignite.hadoop.fs.CachingHadoopFileSystemFactory',
                'fac', secondFs);

            factoryBean.stringProperty('uri')
                .pathProperty('cfgPath', 'configPaths');

            bean.beanProperty('fileSystemFactory', factoryBean);

            cfg.beanProperty('secondaryFileSystem', bean);
        }

        return cfg;
    }

    // Generate IGFS IPC group.
    static igfsIPC(igfs, cfg = this.igfsConfigurationBean(igfs)) {
        if (igfs.ipcEndpointEnabled) {
            const bean = new Bean('org.apache.ignite.igfs.IgfsIpcEndpointConfiguration', 'ipcEndpointConfiguration',
                igfs.ipcEndpointConfiguration, igfsDflts.ipcEndpointConfiguration);

            bean.enumProperty('type')
                .stringProperty('host')
                .intProperty('port')
                .intProperty('memorySize')
                .pathProperty('tokenDirectoryPath')
                .intProperty('threadCount');

            if (bean.nonEmpty())
                cfg.beanProperty('ipcEndpointConfiguration', bean);
        }

        return cfg;
    }

    // Generate IGFS fragmentizer group.
    static igfsFragmentizer(igfs, cfg = this.igfsConfigurationBean(igfs)) {
        if (igfs.fragmentizerEnabled) {
            cfg.intProperty('fragmentizerConcurrentFiles')
                .longProperty('fragmentizerThrottlingBlockLength')
                .longProperty('fragmentizerThrottlingDelay');
        }
        else
            cfg.boolProperty('fragmentizerEnabled');

        return cfg;
    }

    // Generate IGFS Dual mode group.
    static igfsDualMode(igfs, cfg = this.igfsConfigurationBean(igfs)) {
        cfg.intProperty('dualModeMaxPendingPutsSize')
            .emptyBeanProperty('dualModePutExecutorService')
            .intProperty('dualModePutExecutorServiceShutdown');

        return cfg;
    }

    // Generate IGFS miscellaneous group.
    static igfsMisc(igfs, available, cfg = this.igfsConfigurationBean(igfs)) {
        cfg.intProperty('blockSize');

        // Removed in ignite 2.0
        if (available(['1.0.0', '2.0.0']))
            cfg.intProperty('streamBufferSize');

        // Since ignite 2.0
        if (available('2.0.0'))
            cfg.intProperty('streamBufferSize', 'bufferSize');

        // Removed in ignite 2.0
        if (available(['1.0.0', '2.0.0']))
            cfg.intProperty('maxSpaceSize');

        cfg.longProperty('maximumTaskRangeLength')
            .intProperty('managementPort')
            .intProperty('perNodeBatchSize')
            .intProperty('perNodeParallelBatchCount')
            .intProperty('prefetchBlocks')
            .intProperty('sequentialReadsBeforePrefetch');

        // Removed in ignite 2.0
        if (available(['1.0.0', '2.0.0']))
            cfg.intProperty('trashPurgeTimeout');

        cfg.intProperty('colocateMetadata')
            .intProperty('relaxedConsistency')
            .mapProperty('pathModes', 'pathModes');

        // Since ignite 2.0
        if (available('2.0.0'))
            cfg.boolProperty('updateFileLengthOnFlush');

        return cfg;
    }
}
