http://git-wip-us.apache.org/repos/asf/ignite/blob/087f6405/modules/web-console/frontend/app/modules/configuration/generator/JavaTransformer.service.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/modules/configuration/generator/JavaTransformer.service.js b/modules/web-console/frontend/app/modules/configuration/generator/JavaTransformer.service.js new file mode 100644 index 0000000..64d43d8 --- /dev/null +++ b/modules/web-console/frontend/app/modules/configuration/generator/JavaTransformer.service.js @@ -0,0 +1,1721 @@ +/* + * 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 AbstractTransformer from './AbstractTransformer'; +import StringBuilder from './StringBuilder'; + +const STORE_FACTORY = ['org.apache.ignite.cache.store.jdbc.CacheJdbcPojoStoreFactory']; + +// Descriptors for generation of demo data. +const PREDEFINED_QUERIES = [ + { + schema: 'CARS', + type: 'PARKING', + create: [ + 'CREATE TABLE IF NOT EXISTS CARS.PARKING (', + 'ID INTEGER NOT NULL PRIMARY KEY,', + 'NAME VARCHAR(50) NOT NULL,', + 'CAPACITY INTEGER NOT NULL)' + ], + clearQuery: ['DELETE FROM CARS.PARKING'], + insertCntConsts: [{name: 'DEMO_MAX_PARKING_CNT', val: 5, comment: 'How many parkings to generate.'}], + insertPattern: ['INSERT INTO CARS.PARKING(ID, NAME, CAPACITY) VALUES(?, ?, ?)'], + fillInsertParameters(sb) { + sb.append('stmt.setInt(1, id);'); + sb.append('stmt.setString(2, "Parking #" + (id + 1));'); + sb.append('stmt.setInt(3, 10 + rnd.nextInt(20));'); + }, + selectQuery: ['SELECT * FROM PARKING WHERE CAPACITY >= 20'] + }, + { + schema: 'CARS', + type: 'CAR', + create: [ + 'CREATE TABLE IF NOT EXISTS CARS.CAR (', + 'ID INTEGER NOT NULL PRIMARY KEY,', + 'PARKING_ID INTEGER NOT NULL,', + 'NAME VARCHAR(50) NOT NULL);' + ], + clearQuery: ['DELETE FROM CARS.CAR'], + rndRequired: true, + insertCntConsts: [ + {name: 'DEMO_MAX_CAR_CNT', val: 10, comment: 'How many cars to generate.'}, + {name: 'DEMO_MAX_PARKING_CNT', val: 5, comment: 'How many parkings to generate.'} + ], + insertPattern: ['INSERT INTO CARS.CAR(ID, PARKING_ID, NAME) VALUES(?, ?, ?)'], + fillInsertParameters(sb) { + sb.append('stmt.setInt(1, id);'); + sb.append('stmt.setInt(2, rnd.nextInt(DEMO_MAX_PARKING_CNT));'); + sb.append('stmt.setString(3, "Car #" + (id + 1));'); + }, + selectQuery: ['SELECT * FROM CAR WHERE PARKINGID = 2'] + }, + { + type: 'COUNTRY', + create: [ + 'CREATE TABLE IF NOT EXISTS COUNTRY (', + 'ID INTEGER NOT NULL PRIMARY KEY,', + 'NAME VARCHAR(50),', + 'POPULATION INTEGER NOT NULL);' + ], + clearQuery: ['DELETE FROM COUNTRY'], + insertCntConsts: [{name: 'DEMO_MAX_COUNTRY_CNT', val: 5, comment: 'How many countries to generate.'}], + insertPattern: ['INSERT INTO COUNTRY(ID, NAME, POPULATION) VALUES(?, ?, ?)'], + fillInsertParameters(sb) { + sb.append('stmt.setInt(1, id);'); + sb.append('stmt.setString(2, "Country #" + (id + 1));'); + sb.append('stmt.setInt(3, 10000000 + rnd.nextInt(100000000));'); + }, + selectQuery: ['SELECT * FROM COUNTRY WHERE POPULATION BETWEEN 15000000 AND 25000000'] + }, + { + type: 'DEPARTMENT', + create: [ + 'CREATE TABLE IF NOT EXISTS DEPARTMENT (', + 'ID INTEGER NOT NULL PRIMARY KEY,', + 'COUNTRY_ID INTEGER NOT NULL,', + 'NAME VARCHAR(50) NOT NULL);' + ], + clearQuery: ['DELETE FROM DEPARTMENT'], + rndRequired: true, + insertCntConsts: [ + {name: 'DEMO_MAX_DEPARTMENT_CNT', val: 5, comment: 'How many departments to generate.'}, + {name: 'DEMO_MAX_COUNTRY_CNT', val: 5, comment: 'How many countries to generate.'} + ], + insertPattern: ['INSERT INTO DEPARTMENT(ID, COUNTRY_ID, NAME) VALUES(?, ?, ?)'], + fillInsertParameters(sb) { + sb.append('stmt.setInt(1, id);'); + sb.append('stmt.setInt(2, rnd.nextInt(DEMO_MAX_COUNTRY_CNT));'); + sb.append('stmt.setString(3, "Department #" + (id + 1));'); + }, + selectQuery: ['SELECT * FROM DEPARTMENT'] + }, + { + type: 'EMPLOYEE', + create: [ + 'CREATE TABLE IF NOT EXISTS EMPLOYEE (', + 'ID INTEGER NOT NULL PRIMARY KEY,', + 'DEPARTMENT_ID INTEGER NOT NULL,', + 'MANAGER_ID INTEGER,', + 'FIRST_NAME VARCHAR(50) NOT NULL,', + 'LAST_NAME VARCHAR(50) NOT NULL,', + 'EMAIL VARCHAR(50) NOT NULL,', + 'PHONE_NUMBER VARCHAR(50),', + 'HIRE_DATE DATE NOT NULL,', + 'JOB VARCHAR(50) NOT NULL,', + 'SALARY DOUBLE);' + ], + clearQuery: ['DELETE FROM EMPLOYEE'], + rndRequired: true, + insertCntConsts: [ + {name: 'DEMO_MAX_EMPLOYEE_CNT', val: 10, comment: 'How many employees to generate.'}, + {name: 'DEMO_MAX_DEPARTMENT_CNT', val: 5, comment: 'How many departments to generate.'} + ], + customGeneration(sb, conVar, stmtVar) { + sb.append(`${stmtVar} = ${conVar}.prepareStatement("INSERT INTO EMPLOYEE(ID, DEPARTMENT_ID, MANAGER_ID, FIRST_NAME, LAST_NAME, EMAIL, PHONE_NUMBER, HIRE_DATE, JOB, SALARY) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");`); + + sb.emptyLine(); + + sb.startBlock('for (int id = 0; id < DEMO_MAX_EMPLOYEE_CNT; id ++) {'); + + sb.append('int depId = rnd.nextInt(DEMO_MAX_DEPARTMENT_CNT);'); + + sb.emptyLine(); + + sb.append('stmt.setInt(1, DEMO_MAX_DEPARTMENT_CNT + id);'); + sb.append('stmt.setInt(2, depId);'); + sb.append('stmt.setInt(3, depId);'); + sb.append('stmt.setString(4, "First name manager #" + (id + 1));'); + sb.append('stmt.setString(5, "Last name manager#" + (id + 1));'); + sb.append('stmt.setString(6, "Email manager#" + (id + 1));'); + sb.append('stmt.setString(7, "Phone number manager#" + (id + 1));'); + sb.append('stmt.setString(8, "2014-01-01");'); + sb.append('stmt.setString(9, "Job manager #" + (id + 1));'); + sb.append('stmt.setDouble(10, 600.0 + rnd.nextInt(300));'); + + sb.emptyLine(); + + sb.append('stmt.executeUpdate();'); + + sb.endBlock('}'); + }, + selectQuery: ['SELECT * FROM EMPLOYEE WHERE SALARY > 700'] + } +]; + +// Var name generator function. +const beenNameSeed = () => { + let idx = ''; + const names = []; + + return (bean) => { + let name; + + while (_.includes(names, name = `${bean.id}${idx ? '_' + idx : idx}`)) + idx++; + + names.push(name); + + return name; + }; +}; + +export default ['JavaTypes', 'igniteEventGroups', 'IgniteConfigurationGenerator', (JavaTypes, eventGroups, generator) => { + class JavaTransformer extends AbstractTransformer { + static generator = generator; + + // Mapping for objects to method call. + static METHOD_MAPPING = { + 'org.apache.ignite.configuration.CacheConfiguration': { + id: (ccfg) => JavaTypes.toJavaName('cache', ccfg.findProperty('name').value), + args: '', + generator: (sb, id, ccfg) => { + const cacheName = ccfg.findProperty('name').value; + const dataSources = JavaTransformer.collectDataSources(ccfg); + + const javadoc = [ + `Create configuration for cache "${cacheName}".`, + '', + '@return Configured cache.' + ]; + + if (dataSources.length) + javadoc.push('@throws Exception if failed to create cache configuration.'); + + JavaTransformer.commentBlock(sb, ...javadoc); + sb.startBlock(`public static CacheConfiguration ${id}()${dataSources.length ? ' throws Exception' : ''} {`); + + JavaTransformer.constructBean(sb, ccfg, [], true); + + sb.emptyLine(); + sb.append(`return ${ccfg.id};`); + + sb.endBlock('}'); + + return sb; + } + }, + 'org.apache.ignite.cache.store.jdbc.JdbcType': { + id: (type) => JavaTypes.toJavaName('jdbcType', JavaTypes.shortClassName(type.findProperty('valueType').value)), + args: 'ccfg.getName()', + generator: (sb, name, jdbcType) => { + const javadoc = [ + `Create JDBC type for "${name}".`, + '', + '@param cacheName Cache name.', + '@return Configured JDBC type.' + ]; + + JavaTransformer.commentBlock(sb, ...javadoc); + sb.startBlock(`private static JdbcType ${name}(String cacheName) {`); + + const cacheName = jdbcType.findProperty('cacheName'); + + cacheName.clsName = 'var'; + cacheName.value = 'cacheName'; + + JavaTransformer.constructBean(sb, jdbcType); + + sb.emptyLine(); + sb.append(`return ${jdbcType.id};`); + + sb.endBlock('}'); + + return sb; + } + } + }; + + // Append comment line. + static comment(sb, ...lines) { + _.forEach(lines, (line) => sb.append(`// ${line}`)); + } + + // Append comment block. + static commentBlock(sb, ...lines) { + if (lines.length === 1) + sb.append(`/** ${_.head(lines)} **/`); + else { + sb.append('/**'); + + _.forEach(lines, (line) => sb.append(` * ${line}`)); + + sb.append(' **/'); + } + } + + /** + * @param {Bean} bean + */ + static _newBean(bean) { + const shortClsName = JavaTypes.shortClassName(bean.clsName); + + if (_.isEmpty(bean.arguments)) + return `new ${shortClsName}()`; + + const args = _.map(bean.arguments, (arg) => { + switch (arg.clsName) { + case 'MAP': + return arg.id; + case 'BEAN': + return this._newBean(arg.value); + default: + return this._toObject(arg.clsName, arg.value); + } + }); + + if (bean.factoryMtd) + return `${shortClsName}.${bean.factoryMtd}(${args.join(', ')})`; + + return `new ${shortClsName}(${args.join(', ')})`; + } + + /** + * @param {StringBuilder} sb + * @param {String} parentId + * @param {String} propertyName + * @param {String} value + * @private + */ + static _setProperty(sb, parentId, propertyName, value) { + sb.append(`${parentId}.set${_.upperFirst(propertyName)}(${value});`); + } + + /** + * @param {StringBuilder} sb + * @param {Array.<String>} vars + * @param {Boolean} limitLines + * @param {Bean} bean + * @param {String} id + + * @private + */ + static constructBean(sb, bean, vars = [], limitLines = false, id = bean.id) { + _.forEach(bean.arguments, (arg) => { + switch (arg.clsName) { + case 'MAP': + this._constructMap(sb, arg, vars); + + sb.emptyLine(); + + break; + default: + if (this._isBean(arg.clsName) && arg.value.isComplex()) { + this.constructBean(sb, arg.value, vars, limitLines); + + sb.emptyLine(); + } + } + }); + + const clsName = JavaTypes.shortClassName(bean.clsName); + + sb.append(`${this.varInit(clsName, id, vars)} = ${this._newBean(bean)};`); + + if (_.nonEmpty(bean.properties)) { + sb.emptyLine(); + + this._setProperties(sb, bean, vars, limitLines, id); + } + } + + /** + * @param {StringBuilder} sb + * @param {Bean} bean + * @param {Array.<String>} vars + * @param {Boolean} limitLines + * @private + */ + static constructStoreFactory(sb, bean, vars, limitLines = false) { + const shortClsName = JavaTypes.shortClassName(bean.clsName); + + if (_.includes(vars, bean.id)) + sb.append(`${bean.id} = ${this._newBean(bean)};`); + else { + vars.push(bean.id); + + sb.append(`${shortClsName} ${bean.id} = ${this._newBean(bean)};`); + } + + sb.emptyLine(); + + sb.startBlock(`${bean.id}.setDataSourceFactory(new Factory<DataSource>() {`); + this.commentBlock(sb, '{@inheritDoc}'); + sb.startBlock('@Override public DataSource create() {'); + + sb.append(`return DataSources.INSTANCE_${bean.findProperty('dataSourceBean').id};`); + + sb.endBlock('};'); + sb.endBlock('});'); + + const storeFactory = _.cloneDeep(bean); + + _.remove(storeFactory.properties, (p) => _.includes(['dataSourceBean'], p.name)); + + if (storeFactory.properties.length) { + sb.emptyLine(); + + this._setProperties(sb, storeFactory, vars, limitLines); + } + } + + static _isBean(clsName) { + return JavaTypes.nonBuiltInClass(clsName) && JavaTypes.nonEnum(clsName) && _.includes(clsName, '.'); + } + + static _toObject(clsName, val) { + const items = _.isArray(val) ? val : [val]; + + return _.map(items, (item) => { + if (_.isNil(item)) + return 'null'; + + switch (clsName) { + case 'var': + return item; + case 'byte': + return `(byte) ${item}`; + case 'float': + return `${item}f`; + case 'long': + return `${item}L`; + case 'java.io.Serializable': + case 'java.lang.String': + return `"${item}"`; + case 'PATH': + return `"${item.replace(/\\/g, '\\\\')}"`; + case 'java.lang.Class': + return `${JavaTypes.shortClassName(item)}.class`; + case 'java.util.UUID': + return `UUID.fromString("${item}")`; + case 'PROPERTY_CHAR': + return `props.getProperty("${item}").toCharArray()`; + case 'PROPERTY': + return `props.getProperty("${item}")`; + default: + if (this._isBean(clsName)) { + if (item.isComplex()) + return item.id; + + return this._newBean(item); + } + + if (JavaTypes.nonEnum(clsName)) + return item; + + return `${JavaTypes.shortClassName(clsName)}.${item}`; + } + }); + } + + static _constructBeans(sb, type, items, vars, limitLines) { + if (this._isBean(type)) { + // Construct objects inline for preview or simple objects. + const mapper = this.METHOD_MAPPING[type]; + + const nextId = mapper ? mapper.id : beenNameSeed(); + + // Prepare objects refs. + return _.map(items, (item) => { + if (limitLines && mapper) + return mapper.id(item) + (limitLines ? `(${mapper.args})` : ''); + + if (item.isComplex()) { + const id = nextId(item); + + this.constructBean(sb, item, vars, limitLines, id); + + sb.emptyLine(); + + return id; + } + + return this._newBean(item); + }); + } + + return this._toObject(type, items); + } + + /** + * + * @param sb + * @param parentId + * @param arrProp + * @param vars + * @param limitLines + * @private + */ + static _setVarArg(sb, parentId, arrProp, vars, limitLines) { + const refs = this._constructBeans(sb, arrProp.typeClsName, arrProp.items, vars, limitLines); + + // Set refs to property. + if (refs.length === 1) + this._setProperty(sb, parentId, arrProp.name, _.head(refs)); + else { + sb.startBlock(`${parentId}.set${_.upperFirst(arrProp.name)}(`); + + const lastIdx = refs.length - 1; + + _.forEach(refs, (ref, idx) => { + sb.append(ref + (lastIdx !== idx ? ',' : '')); + }); + + sb.endBlock(');'); + } + } + + /** + * + * @param sb + * @param parentId + * @param arrProp + * @param vars + * @param limitLines + * @private + */ + static _setArray(sb, parentId, arrProp, vars, limitLines) { + const refs = this._constructBeans(sb, arrProp.typeClsName, arrProp.items, vars, limitLines); + + const arrType = JavaTypes.shortClassName(arrProp.typeClsName); + + // Set refs to property. + sb.startBlock(`${parentId}.set${_.upperFirst(arrProp.name)}(new ${arrType}[] {`); + + const lastIdx = refs.length - 1; + + _.forEach(refs, (ref, idx) => sb.append(ref + (lastIdx !== idx ? ',' : ''))); + + sb.endBlock('});'); + } + + static _constructMap(sb, map, vars = []) { + const keyClsName = JavaTypes.shortClassName(map.keyClsName); + const valClsName = JavaTypes.shortClassName(map.valClsName); + + const mapClsName = map.ordered ? 'LinkedHashMap' : 'HashMap'; + + const type = `${mapClsName}<${keyClsName}, ${valClsName}>`; + + sb.append(`${this.varInit(type, map.id, vars)} = new ${mapClsName}<>();`); + + sb.emptyLine(); + + _.forEach(map.entries, (entry) => { + const key = this._toObject(map.keyClsName, entry[map.keyField]); + const val = entry[map.valField]; + + if (_.isArray(val) && map.valClsName === 'java.lang.String') { + if (val.length > 1) { + sb.startBlock(`${map.id}.put(${key},`); + + _.forEach(val, (line, idx) => { + sb.append(`"${line}"${idx !== val.length - 1 ? ' +' : ''}`); + }); + + sb.endBlock(');'); + } + else + sb.append(`${map.id}.put(${key}, ${this._toObject(map.valClsName, _.head(val))});`); + } + else + sb.append(`${map.id}.put(${key}, ${this._toObject(map.valClsName, val)});`); + }); + } + + static varInit(type, id, vars) { + if (_.includes(vars, id)) + return id; + + vars.push(id); + + return `${type} ${id}`; + } + + /** + * + * @param {StringBuilder} sb + * @param {Bean} bean + * @param {String} id + * @param {Array.<String>} vars + * @param {Boolean} limitLines + * @returns {StringBuilder} + */ + static _setProperties(sb = new StringBuilder(), bean, vars = [], limitLines = false, id = bean.id) { + _.forEach(bean.properties, (prop, idx) => { + switch (prop.clsName) { + case 'DATA_SOURCE': + this._setProperty(sb, id, 'dataSource', `DataSources.INSTANCE_${prop.id}`); + + break; + case 'EVENT_TYPES': + if (prop.eventTypes.length === 1) + this._setProperty(sb, id, prop.name, _.head(prop.eventTypes)); + else { + sb.append(`int[] ${prop.id} = new int[${_.head(prop.eventTypes)}.length`); + + _.forEach(_.tail(prop.eventTypes), (evtGrp) => { + sb.append(` + ${evtGrp}.length`); + }); + + sb.append('];'); + + sb.emptyLine(); + + sb.append('int k = 0;'); + + _.forEach(prop.eventTypes, (evtGrp, evtIdx) => { + sb.emptyLine(); + + sb.append(`System.arraycopy(${evtGrp}, 0, ${prop.id}, k, ${evtGrp}.length);`); + + if (evtIdx < prop.eventTypes.length - 1) + sb.append(`k += ${evtGrp}.length;`); + }); + + sb.emptyLine(); + + sb.append(`cfg.setIncludeEventTypes(${prop.id});`); + } + + break; + case 'ARRAY': + if (prop.varArg) + this._setVarArg(sb, id, prop, vars, limitLines); + else + this._setArray(sb, id, prop, vars, limitLines); + + break; + case 'COLLECTION': + const nonBean = !this._isBean(prop.typeClsName); + + if (nonBean && prop.implClsName === 'java.util.ArrayList') { + const items = _.map(prop.items, (item) => this._toObject(prop.typeClsName, item)); + + if (items.length > 1) { + sb.startBlock(`${id}.set${_.upperFirst(prop.name)}(Arrays.asList(`); + + _.forEach(items, (item, i) => sb.append(item + (i !== items.length - 1 ? ',' : ''))); + + sb.endBlock('));'); + } + else + this._setProperty(sb, id, prop.name, `Arrays.asList(${items})`); + } + else { + const colTypeClsName = JavaTypes.shortClassName(prop.typeClsName); + const implClsName = JavaTypes.shortClassName(prop.implClsName); + + sb.append(`${this.varInit(`${implClsName}<${colTypeClsName}>`, prop.id, vars)} = new ${implClsName}<>();`); + + sb.emptyLine(); + + if (nonBean) { + _.forEach(this._toObject(colTypeClsName, prop.items), (item) => { + sb.append(`${prop.id}.add("${item}");`); + + sb.emptyLine(); + }); + } + else { + _.forEach(prop.items, (item) => { + this.constructBean(sb, item, vars, limitLines); + + sb.append(`${prop.id}.add(${item.id});`); + + sb.emptyLine(); + }); + } + + this._setProperty(sb, id, prop.name, prop.id); + } + + break; + case 'MAP': + this._constructMap(sb, prop, vars); + + if (_.nonEmpty(prop.entries)) + sb.emptyLine(); + + this._setProperty(sb, id, prop.name, prop.id); + + break; + case 'java.util.Properties': + sb.append(`${this.varInit('Properties', prop.id, vars)} = new Properties();`); + + if (_.nonEmpty(prop.entries)) + sb.emptyLine(); + + _.forEach(prop.entries, (entry) => { + const key = this._toObject('java.lang.String', entry.name); + const val = this._toObject('java.lang.String', entry.value); + + sb.append(`${prop.id}.setProperty(${key}, ${val});`); + }); + + sb.emptyLine(); + + this._setProperty(sb, id, prop.name, prop.id); + + break; + case 'BEAN': + const embedded = prop.value; + + if (_.includes(STORE_FACTORY, embedded.clsName)) { + this.constructStoreFactory(sb, embedded, vars, limitLines); + + sb.emptyLine(); + + this._setProperty(sb, id, prop.name, embedded.id); + } + else if (embedded.isComplex()) { + this.constructBean(sb, embedded, vars, limitLines); + + sb.emptyLine(); + + this._setProperty(sb, id, prop.name, embedded.id); + } + else + this._setProperty(sb, id, prop.name, this._newBean(embedded)); + + break; + default: + this._setProperty(sb, id, prop.name, this._toObject(prop.clsName, prop.value)); + } + + this._emptyLineIfNeeded(sb, bean.properties, idx); + }); + + return sb; + } + + static collectBeanImports(bean) { + const imports = [bean.clsName]; + + _.forEach(bean.arguments, (arg) => { + switch (arg.clsName) { + case 'BEAN': + imports.push(...this.collectPropertiesImports(arg.value.properties)); + + break; + case 'java.lang.Class': + imports.push(JavaTypes.fullClassName(arg.value)); + + break; + default: + imports.push(arg.clsName); + } + }); + + imports.push(...this.collectPropertiesImports(bean.properties)); + + if (_.includes(STORE_FACTORY, bean.clsName)) + imports.push('javax.sql.DataSource', 'javax.cache.configuration.Factory'); + + return imports; + } + + /** + * @param {Array.<Object>} props + * @returns {Array.<String>} + */ + static collectPropertiesImports(props) { + const imports = []; + + _.forEach(props, (prop) => { + switch (prop.clsName) { + case 'DATA_SOURCE': + imports.push(prop.value.clsName); + + break; + case 'PROPERTY': + case 'PROPERTY_CHAR': + imports.push('java.io.InputStream', 'java.util.Properties'); + + break; + case 'BEAN': + imports.push(...this.collectBeanImports(prop.value)); + + break; + case 'ARRAY': + imports.push(prop.typeClsName); + + if (this._isBean(prop.typeClsName)) + _.forEach(prop.items, (item) => imports.push(...this.collectBeanImports(item))); + + break; + case 'COLLECTION': + imports.push(prop.typeClsName); + + if (this._isBean(prop.typeClsName)) { + _.forEach(prop.items, (item) => imports.push(...this.collectBeanImports(item))); + + imports.push(prop.implClsName); + } + else if (prop.implClsName === 'java.util.ArrayList') + imports.push('java.util.Arrays'); + else + imports.push(prop.implClsName); + + break; + case 'MAP': + imports.push(prop.ordered ? 'java.util.LinkedHashMap' : 'java.util.HashMap'); + imports.push(prop.keyClsName); + imports.push(prop.valClsName); + + break; + default: + if (!JavaTypes.nonEnum(prop.clsName)) + imports.push(prop.clsName); + } + }); + + return imports; + } + + static _prepareImports(imports) { + return _.sortedUniq(_.sortBy(_.filter(imports, (cls) => !cls.startsWith('java.lang.') && _.includes(cls, '.')))); + } + + /** + * @param {Bean} bean + * @returns {Array.<String>} + */ + static collectStaticImports(bean) { + const imports = []; + + _.forEach(bean.properties, (prop) => { + switch (prop.clsName) { + case 'EVENT_TYPES': + _.forEach(prop.eventTypes, (value) => { + const evtGrp = _.find(eventGroups, {value}); + + imports.push(`${evtGrp.class}.${evtGrp.value}`); + }); + + break; + default: + // No-op. + } + }); + + return imports; + } + + /** + * @param {Bean} bean + * @returns {Object} + */ + static collectBeansWithMapping(bean) { + const beans = {}; + + _.forEach(bean.properties, (prop) => { + switch (prop.clsName) { + case 'BEAN': + _.merge(beans, this.collectBeansWithMapping(prop.value)); + + break; + case 'ARRAY': + if (this._isBean(prop.typeClsName)) { + const mapping = this.METHOD_MAPPING[prop.typeClsName]; + + _.reduce(prop.items, (acc, item) => { + if (mapping) { + acc[mapping.id(item)] = item; + + _.merge(acc, this.collectBeansWithMapping(item)); + } + return acc; + }, beans); + } + + break; + default: + // No-op. + } + }); + + return beans; + } + + /** + * Build Java startup class with configuration. + * + * @param {Bean} cfg + * @param pkg Package name. + * @param {String} clsName Class name for generate factory class otherwise generate code snippet. + * @param {Array.<Object>} clientNearCaches Is client node. + * @returns {StringBuilder} + */ + static igniteConfiguration(cfg, pkg, clsName, clientNearCaches) { + const sb = new StringBuilder(); + + sb.append(`package ${pkg};`); + sb.emptyLine(); + + const imports = this.collectBeanImports(cfg); + + if (_.nonEmpty(clientNearCaches)) + imports.push('org.apache.ignite.configuration.NearCacheConfiguration'); + + if (_.includes(imports, 'oracle.jdbc.pool.OracleDataSource')) + imports.push('java.sql.SQLException'); + + const hasProps = this.hasProperties(cfg); + + if (hasProps) + imports.push('java.util.Properties', 'java.io.InputStream'); + + _.forEach(this._prepareImports(imports), (cls) => sb.append(`import ${cls};`)); + + sb.emptyLine(); + + const staticImports = this._prepareImports(this.collectStaticImports(cfg)); + + if (staticImports.length) { + _.forEach(this._prepareImports(staticImports), (cls) => sb.append(`import static ${cls};`)); + + sb.emptyLine(); + } + + this.mainComment(sb); + sb.startBlock(`public class ${clsName} {`); + + // 2. Add external property file + if (hasProps) { + this.commentBlock(sb, 'Secret properties loading.'); + sb.append('private static final Properties props = new Properties();'); + sb.emptyLine(); + sb.startBlock('static {'); + sb.startBlock('try (InputStream in = IgniteConfiguration.class.getClassLoader().getResourceAsStream("secret.properties")) {'); + sb.append('props.load(in);'); + sb.endBlock('}'); + sb.startBlock('catch (Exception ignored) {'); + sb.append('// No-op.'); + sb.endBlock('}'); + sb.endBlock('}'); + sb.emptyLine(); + } + + // 3. Add data sources. + const dataSources = this.collectDataSources(cfg); + + if (dataSources.length) { + this.commentBlock(sb, 'Helper class for datasource creation.'); + sb.startBlock('public static class DataSources {'); + + _.forEach(dataSources, (ds, idx) => { + const dsClsName = JavaTypes.shortClassName(ds.clsName); + + if (idx !== 0) + sb.emptyLine(); + + sb.append(`public static final ${dsClsName} INSTANCE_${ds.id} = create${ds.id}();`); + sb.emptyLine(); + + sb.startBlock(`private static ${dsClsName} create${ds.id}() {`); + + if (dsClsName === 'OracleDataSource') + sb.startBlock('try {'); + + this.constructBean(sb, ds); + + sb.emptyLine(); + sb.append(`return ${ds.id};`); + + if (dsClsName === 'OracleDataSource') { + sb.endBlock('}'); + sb.startBlock('catch (SQLException ex) {'); + sb.append('throw new Error(ex);'); + sb.endBlock('}'); + } + + sb.endBlock('}'); + }); + + sb.endBlock('}'); + + sb.emptyLine(); + } + + _.forEach(clientNearCaches, (cache) => { + this.commentBlock(sb, `Configuration of near cache for cache: ${cache.name}.`, + '', + '@return Near cache configuration.', + '@throws Exception If failed to construct near cache configuration instance.' + ); + + const nearCacheBean = generator.cacheNearClient(cache); + + sb.startBlock(`public static NearCacheConfiguration ${nearCacheBean.id}() throws Exception {`); + + this.constructBean(sb, nearCacheBean); + sb.emptyLine(); + + sb.append(`return ${nearCacheBean.id};`); + sb.endBlock('}'); + + sb.emptyLine(); + }); + + this.commentBlock(sb, 'Configure grid.', + '', + '@return Ignite configuration.', + '@throws Exception If failed to construct Ignite configuration instance.' + ); + sb.startBlock('public static IgniteConfiguration createConfiguration() throws Exception {'); + + this.constructBean(sb, cfg, [], true); + + sb.emptyLine(); + + sb.append(`return ${cfg.id};`); + + sb.endBlock('}'); + + const beans = this.collectBeansWithMapping(cfg); + + _.forEach(beans, (bean, id) => { + sb.emptyLine(); + + this.METHOD_MAPPING[bean.clsName].generator(sb, id, bean); + }); + + sb.endBlock('}'); + + return sb; + } + + static cluster(cluster, pkg, clsName, client) { + const cfg = this.generator.igniteConfiguration(cluster, client); + + const clientNearCaches = client ? _.filter(cluster.caches, (cache) => _.get(cache, 'clientNearConfiguration.enabled')) : []; + + return this.igniteConfiguration(cfg, pkg, clsName, clientNearCaches); + } + + /** + * Generate source code for type by its domain model. + * + * @param fullClsName Full class name. + * @param fields Fields. + * @param addConstructor If 'true' then empty and full constructors should be generated. + * @returns {StringBuilder} + */ + static pojo(fullClsName, fields, addConstructor) { + const dotIdx = fullClsName.lastIndexOf('.'); + + const pkg = fullClsName.substring(0, dotIdx); + const clsName = fullClsName.substring(dotIdx + 1); + + const sb = new StringBuilder(); + + sb.append(`package ${pkg};`); + sb.emptyLine(); + + const imports = ['java.io.Serializable']; + + _.forEach(fields, (field) => imports.push(JavaTypes.fullClassName(field.javaFieldType))); + + _.forEach(this._prepareImports(imports), (cls) => sb.append(`import ${cls};`)); + + sb.emptyLine(); + + this.mainComment(sb, + `${clsName} definition.`, + '' + ); + sb.startBlock(`public class ${clsName} implements Serializable {`); + sb.append('/** */'); + sb.append('private static final long serialVersionUID = 0L;'); + sb.emptyLine(); + + // Generate fields declaration. + _.forEach(fields, (field) => { + const fldName = field.javaFieldName; + const fldType = JavaTypes.shortClassName(field.javaFieldType); + + sb.append(`/** Value for ${fldName}. */`); + sb.append(`private ${fldType} ${fldName};`); + + sb.emptyLine(); + }); + + // Generate constructors. + if (addConstructor) { + this.commentBlock(sb, 'Empty constructor.'); + sb.startBlock(`public ${clsName}() {`); + this.comment(sb, 'No-op.'); + sb.endBlock('}'); + + sb.emptyLine(); + + this.commentBlock(sb, 'Full constructor.'); + + const arg = (field) => { + const fldType = JavaTypes.shortClassName(field.javaFieldType); + + return `${fldType} ${field.javaFieldName}`; + }; + + sb.startBlock(`public ${clsName}(${arg(_.head(fields))}${fields.length === 1 ? ') {' : ','}`); + + _.forEach(_.tail(fields), (field, idx) => { + sb.append(`${arg(field)}${idx !== fields.length - 1 ? ',' : ') {'}`); + }); + + _.forEach(fields, (field) => sb.append(`this.${field.javaFieldName} = ${field.javaFieldName};`)); + + sb.endBlock('}'); + + sb.emptyLine(); + } + + // Generate getters and setters methods. + _.forEach(fields, (field) => { + const fldType = JavaTypes.shortClassName(field.javaFieldType); + const fldName = field.javaFieldName; + + this.commentBlock(sb, + `Gets ${fldName}`, + '', + `@return Value for ${fldName}.` + ); + sb.startBlock(`public ${fldType} ${JavaTypes.toJavaName('get', fldName)}() {`); + sb.append('return ' + fldName + ';'); + sb.endBlock('}'); + + sb.emptyLine(); + + this.commentBlock(sb, + `Sets ${fldName}`, + '', + `@param ${fldName} New value for ${fldName}.` + ); + sb.startBlock(`public void ${JavaTypes.toJavaName('set', fldName)}(${fldType} ${fldName}) {`); + sb.append(`this.${fldName} = ${fldName};`); + sb.endBlock('}'); + + sb.emptyLine(); + }); + + // Generate equals() method. + this.commentBlock(sb, '{@inheritDoc}'); + sb.startBlock('@Override public boolean equals(Object o) {'); + sb.startBlock('if (this == o)'); + sb.append('return true;'); + + sb.endBlock(''); + + sb.startBlock(`if (!(o instanceof ${clsName}))`); + sb.append('return false;'); + + sb.endBlock(''); + + sb.append(`${clsName} that = (${clsName})o;`); + + _.forEach(fields, (field) => { + sb.emptyLine(); + + const javaName = field.javaFieldName; + const javaType = field.javaFieldType; + + switch (javaType) { + case 'float': + sb.startBlock(`if (Float.compare(${javaName}, that.${javaName}) != 0)`); + + break; + case 'double': + sb.startBlock(`if (Double.compare(${javaName}, that.${javaName}) != 0)`); + + break; + default: + if (JavaTypes.isJavaPrimitive(javaType)) + sb.startBlock('if (' + javaName + ' != that.' + javaName + ')'); + else + sb.startBlock('if (' + javaName + ' != null ? !' + javaName + '.equals(that.' + javaName + ') : that.' + javaName + ' != null)'); + } + + sb.append('return false;'); + + sb.endBlock(''); + }); + + sb.append('return true;'); + sb.endBlock('}'); + + sb.emptyLine(); + + // Generate hashCode() method. + this.commentBlock(sb, '{@inheritDoc}'); + sb.startBlock('@Override public int hashCode() {'); + + let first = true; + let tempVar = false; + + _.forEach(fields, (field) => { + const javaName = field.javaFieldName; + const javaType = field.javaFieldType; + + let fldHashCode; + + switch (javaType) { + case 'boolean': + fldHashCode = `${javaName} ? 1 : 0`; + + break; + case 'byte': + case 'short': + fldHashCode = `(int)${javaName}`; + + break; + case 'int': + fldHashCode = `${javaName}`; + + break; + case 'long': + fldHashCode = `(int)(${javaName} ^ (${javaName} >>> 32))`; + + break; + case 'float': + fldHashCode = `${javaName} != +0.0f ? Float.floatToIntBits(${javaName}) : 0`; + + break; + case 'double': + sb.append(`${tempVar ? 'ig_hash_temp' : 'long ig_hash_temp'} = Double.doubleToLongBits(${javaName});`); + + tempVar = true; + + fldHashCode = `${javaName} != +0.0f ? Float.floatToIntBits(${javaName}) : 0`; + + break; + default: + fldHashCode = `${javaName} != null ? ${javaName}.hashCode() : 0`; + } + + sb.append(first ? `int res = ${fldHashCode};` : `res = 31 * res + ${fldHashCode.startsWith('(') ? fldHashCode : `(${fldHashCode})`};`); + + first = false; + + sb.emptyLine(); + }); + + sb.append('return res;'); + sb.endBlock('}'); + + sb.emptyLine(); + + this.commentBlock(sb, '{@inheritDoc}'); + sb.startBlock('@Override public String toString() {'); + sb.startBlock(`return "${clsName} [" + `); + + _.forEach(fields, (field, idx) => { + sb.append(`"${field.javaFieldName}=" + ${field.javaFieldName}${idx < fields.length - 1 ? ' + ", " + ' : ' +'}`); + }); + + sb.endBlock('"]";'); + sb.endBlock('}'); + + sb.endBlock('}'); + + return sb.asString(); + } + + /** + * Generate source code for type by its domain models. + * + * @param caches List of caches to generate POJOs for. + * @param addConstructor If 'true' then generate constructors. + * @param includeKeyFields If 'true' then include key fields into value POJO. + */ + static pojos(caches, addConstructor, includeKeyFields) { + const pojos = []; + + _.forEach(caches, (cache) => { + _.forEach(cache.domains, (domain) => { + // Process only domains with 'generatePojo' flag and skip already generated classes. + if (domain.generatePojo && !_.find(pojos, {valueType: domain.valueType}) && + // Skip domain models without value fields. + _.nonEmpty(domain.valueFields)) { + const pojo = {}; + + // Key class generation only if key is not build in java class. + if (_.nonNil(domain.keyFields) && domain.keyFields.length > 0) { + pojo.keyType = domain.keyType; + pojo.keyClass = this.pojo(domain.keyType, domain.keyFields, addConstructor); + } + + const valueFields = _.clone(domain.valueFields); + + if (includeKeyFields) { + _.forEach(domain.keyFields, ({fld}) => { + if (!_.find(valueFields, {name: fld.name})) + valueFields.push(fld); + }); + } + + pojo.valueType = domain.valueType; + pojo.valueClass = this.pojo(domain.valueType, valueFields, addConstructor); + + pojos.push(pojo); + } + }); + }); + + return pojos; + } + + // Generate creation and execution of cache query. + static _multilineQuery(sb, query, prefix, postfix) { + if (_.isEmpty(query)) + return; + + _.forEach(query, (line, ix) => { + if (ix === 0) { + if (query.length === 1) + sb.append(`${prefix}"${line}"${postfix}`); + else + sb.startBlock(`${prefix}"${line}" +`); + } + else + sb.append(`"${line}"${ix === query.length - 1 ? postfix : ' +'}`); + }); + + if (query.length > 1) + sb.endBlock(''); + else + sb.emptyLine(); + } + + // Generate creation and execution of prepared statement. + static _prepareStatement(sb, conVar, query) { + this._multilineQuery(sb, query, `${conVar}.prepareStatement(`, ').executeUpdate();'); + } + + static demoStartup(sb, cluster, shortFactoryCls) { + const cachesWithDataSource = _.filter(cluster.caches, (cache) => { + const kind = _.get(cache, 'cacheStoreFactory.kind'); + + if (kind) { + const store = cache.cacheStoreFactory[kind]; + + return (store.connectVia === 'DataSource' || _.isNil(store.connectVia)) && store.dialect; + } + + return false; + }); + + const uniqDomains = []; + + // Prepare array of cache and his demo domain model list. Every domain is contained only in first cache. + const demoTypes = _.reduce(cachesWithDataSource, (acc, cache) => { + const domains = _.filter(cache.domains, (domain) => _.nonEmpty(domain.valueFields) && + !_.includes(uniqDomains, domain)); + + if (_.nonEmpty(domains)) { + uniqDomains.push(...domains); + + acc.push({ + cache, + domains + }); + } + + return acc; + }, []); + + if (_.nonEmpty(demoTypes)) { + // Group domain modes by data source + const typeByDs = _.groupBy(demoTypes, ({cache}) => cache.cacheStoreFactory[cache.cacheStoreFactory.kind].dataSourceBean); + + let rndNonDefined = true; + + const generatedConsts = []; + + _.forEach(typeByDs, (types) => { + _.forEach(types, (type) => { + _.forEach(type.domains, (domain) => { + const valType = domain.valueType.toUpperCase(); + + const desc = _.find(PREDEFINED_QUERIES, (qry) => valType.endsWith(qry.type)); + + if (desc) { + if (rndNonDefined && desc.rndRequired) { + this.commentBlock(sb, 'Random generator for demo data.'); + sb.append('private static final Random rnd = new Random();'); + + sb.emptyLine(); + + rndNonDefined = false; + } + + _.forEach(desc.insertCntConsts, (cnt) => { + if (!_.includes(generatedConsts, cnt.name)) { + this.commentBlock(sb, cnt.comment); + sb.append(`private static final int ${cnt.name} = ${cnt.val};`); + + sb.emptyLine(); + + generatedConsts.push(cnt.name); + } + }); + } + }); + }); + }); + + // Generation of fill database method + this.commentBlock(sb, 'Fill data for Demo.'); + sb.startBlock('private static void prepareDemoData() throws SQLException {'); + + let firstDs = true; + + _.forEach(typeByDs, (types, ds) => { + const conVar = ds + 'Con'; + + if (firstDs) + firstDs = false; + else + sb.emptyLine(); + + sb.startBlock(`try (Connection ${conVar} = ${shortFactoryCls}.DataSources.INSTANCE_${ds}.getConnection()) {`); + + let first = true; + let stmtFirst = true; + + _.forEach(types, (type) => { + _.forEach(type.domains, (domain) => { + const valType = domain.valueType.toUpperCase(); + + const desc = _.find(PREDEFINED_QUERIES, (qry) => valType.endsWith(qry.type)); + + if (desc) { + if (first) + first = false; + else + sb.emptyLine(); + + this.comment(sb, `Generate ${desc.type}.`); + + if (desc.schema) + this._prepareStatement(sb, conVar, [`CREATE SCHEMA IF NOT EXISTS ${desc.schema}`]); + + this._prepareStatement(sb, conVar, desc.create); + + this._prepareStatement(sb, conVar, desc.clearQuery); + + let stmtVar = 'stmt'; + + if (stmtFirst) { + stmtFirst = false; + + stmtVar = 'PreparedStatement stmt'; + } + + if (_.isFunction(desc.customGeneration)) + desc.customGeneration(sb, conVar, stmtVar); + else { + sb.append(`${stmtVar} = ${conVar}.prepareStatement("${desc.insertPattern}");`); + + sb.emptyLine(); + + sb.startBlock(`for (int id = 0; id < ${desc.insertCntConsts[0].name}; id ++) {`); + + desc.fillInsertParameters(sb); + + sb.emptyLine(); + + sb.append('stmt.executeUpdate();'); + + sb.endBlock('}'); + } + + sb.emptyLine(); + + sb.append(`${conVar}.commit();`); + } + }); + }); + + sb.endBlock('}'); + }); + + sb.endBlock('}'); + + sb.emptyLine(); + + this.commentBlock(sb, 'Print result table to console.'); + sb.startBlock('private static void printResult(List<Cache.Entry<Object, Object>> rows) {'); + sb.append('for (Cache.Entry<Object, Object> row: rows)'); + sb.append(' System.out.println(row);'); + sb.endBlock('}'); + + sb.emptyLine(); + + // Generation of execute queries method. + this.commentBlock(sb, 'Run demo.'); + sb.startBlock('private static void runDemo(Ignite ignite) throws SQLException {'); + + const getType = (fullType) => fullType.substr(fullType.lastIndexOf('.') + 1); + + const cacheLoaded = []; + let rowVariableDeclared = false; + firstDs = true; + + _.forEach(typeByDs, (types, ds) => { + const conVar = ds + 'Con'; + + if (firstDs) + firstDs = false; + else + sb.emptyLine(); + + sb.startBlock(`try (Connection ${conVar} = ${shortFactoryCls}.DataSources.INSTANCE_${ds}.getConnection()) {`); + + let first = true; + + _.forEach(types, (type) => { + _.forEach(type.domains, (domain) => { + const valType = domain.valueType.toUpperCase(); + + const desc = _.find(PREDEFINED_QUERIES, (qry) => valType.endsWith(qry.type)); + + if (desc) { + if (_.isEmpty(desc.selectQuery)) + return; + + if (first) + first = false; + else + sb.emptyLine(); + + const cacheName = type.cache.name; + + if (!_.includes(cacheLoaded, cacheName)) { + sb.append(`ignite.cache("${cacheName}").loadCache(null);`); + + sb.emptyLine(); + + cacheLoaded.push(cacheName); + } + + const varRows = rowVariableDeclared ? 'rows' : 'List<Cache.Entry<Object, Object>> rows'; + + this._multilineQuery(sb, desc.selectQuery, `${varRows} = ignite.cache("${cacheName}").query(new SqlQuery<>("${getType(domain.valueType)}", `, ')).getAll();'); + + sb.append('printResult(rows);'); + + rowVariableDeclared = true; + } + }); + }); + + sb.endBlock('}'); + }); + + sb.endBlock('}'); + } + } + + /** + * Function to generate java class for node startup with cluster configuration. + * + * @param {Object} cluster Cluster to process. + * @param {String} fullClsName Full class name. + * @param {String} cfgRef Config. + * @param {String} [factoryCls] fully qualified class name of configuration factory. + * @param {Array.<Object>} [clientNearCaches] Is client node. + */ + static nodeStartup(cluster, fullClsName, cfgRef, factoryCls, clientNearCaches) { + const dotIdx = fullClsName.lastIndexOf('.'); + + const pkg = fullClsName.substring(0, dotIdx); + const clsName = fullClsName.substring(dotIdx + 1); + + const demo = clsName === 'DemoStartup'; + + const sb = new StringBuilder(); + + const imports = ['org.apache.ignite.Ignition', 'org.apache.ignite.Ignite']; + + if (demo) { + imports.push('org.h2.tools.Server', 'java.sql.Connection', 'java.sql.PreparedStatement', + 'java.sql.SQLException', 'java.util.Random', 'java.util.List', 'javax.cache.Cache', + 'org.apache.ignite.cache.query.SqlQuery'); + } + + let shortFactoryCls; + + if (factoryCls) { + imports.push(factoryCls); + + shortFactoryCls = JavaTypes.shortClassName(factoryCls); + } + + sb.append(`package ${pkg};`) + .emptyLine(); + + _.forEach(this._prepareImports(imports), (cls) => sb.append(`import ${cls};`)); + sb.emptyLine(); + + if (demo) { + this.mainComment(sb, + 'To start demo configure data sources in secret.properties file.', + 'For H2 database it should be like following:', + 'dsH2.jdbc.url=jdbc:h2:tcp://localhost/mem:DemoDB;DB_CLOSE_DELAY=-1', + 'dsH2.jdbc.username=sa', + 'dsH2.jdbc.password=', + '' + ); + } + else + this.mainComment(sb); + + sb.startBlock(`public class ${clsName} {`); + + if (demo && shortFactoryCls) + this.demoStartup(sb, cluster, shortFactoryCls); + + this.commentBlock(sb, + 'Start up node with specified configuration.', + '', + '@param args Command line arguments, none required.', + '@throws Exception If failed.' + ); + sb.startBlock('public static void main(String[] args) throws Exception {'); + + if (demo) { + sb.startBlock('try {'); + sb.append('// Start H2 database server.'); + sb.append('Server.createTcpServer("-tcpDaemon").start();'); + sb.endBlock('}'); + sb.startBlock('catch (SQLException ignore) {'); + sb.append('// No-op.'); + sb.endBlock('}'); + + sb.emptyLine(); + } + + if ((_.nonEmpty(clientNearCaches) || demo) && shortFactoryCls) { + sb.append(`Ignite ignite = Ignition.start(${cfgRef});`); + + _.forEach(clientNearCaches, (cache, idx) => { + sb.emptyLine(); + + if (idx === 0) + sb.append('// Demo of near cache creation on client node.'); + + const nearCacheMtd = JavaTypes.toJavaName('nearConfiguration', cache.name); + + sb.append(`ignite.getOrCreateCache(${shortFactoryCls}.${cache.name}(), ${shortFactoryCls}.${nearCacheMtd}());`); + }); + } + else + sb.append(`Ignition.start(${cfgRef});`); + + if (demo) { + sb.emptyLine(); + + sb.append('prepareDemoData();'); + + sb.emptyLine(); + + sb.append('runDemo(ignite);'); + } + + sb.endBlock('}'); + + sb.endBlock('}'); + + return sb.asString(); + } + + /** + * Function to generate java class for load caches. + * + * @param caches Caches to load. + * @param pkg Class package name. + * @param clsName Class name. + * @param {String} cfgRef Config. + */ + static loadCaches(caches, pkg, clsName, cfgRef) { + const sb = new StringBuilder(); + + sb.append(`package ${pkg};`) + .emptyLine(); + + const imports = ['org.apache.ignite.Ignition', 'org.apache.ignite.Ignite']; + + _.forEach(this._prepareImports(imports), (cls) => sb.append(`import ${cls};`)); + sb.emptyLine(); + + this.mainComment(sb); + sb.startBlock(`public class ${clsName} {`); + + this.commentBlock(sb, + '<p>', + 'Utility to load caches from database.', + '<p>', + 'How to use:', + '<ul>', + ' <li>Start cluster.</li>', + ' <li>Start this utility and wait while load complete.</li>', + '</ul>', + '', + '@param args Command line arguments, none required.', + '@throws Exception If failed.' + ); + sb.startBlock('public static void main(String[] args) throws Exception {'); + + sb.startBlock(`try (Ignite ignite = Ignition.start(${cfgRef})) {`); + + sb.append('System.out.println(">>> Loading caches...");'); + + sb.emptyLine(); + + _.forEach(caches, (cache) => { + sb.append('System.out.println(">>> Loading cache: ' + cache.name + '");'); + sb.append('ignite.cache("' + cache.name + '").loadCache(null);'); + + sb.emptyLine(); + }); + + sb.append('System.out.println(">>> All caches loaded!");'); + + sb.endBlock('}'); + + sb.endBlock('}'); + + sb.endBlock('}'); + + return sb.asString(); + } + + /** + * Checks if cluster has demo types. + * + * @param cluster Cluster to check. + * @param demo Is demo enabled. + * @returns {boolean} True if cluster has caches with demo types. + */ + static isDemoConfigured(cluster, demo) { + return demo && _.find(cluster.caches, (cache) => _.find(cache.domains, (domain) => _.find(PREDEFINED_QUERIES, (desc) => domain.valueType.toUpperCase().endsWith(desc.type)))); + } + } + + return JavaTransformer; +}];
http://git-wip-us.apache.org/repos/asf/ignite/blob/087f6405/modules/web-console/frontend/app/modules/configuration/generator/PlatformGenerator.js ---------------------------------------------------------------------- diff --git a/modules/web-console/frontend/app/modules/configuration/generator/PlatformGenerator.js b/modules/web-console/frontend/app/modules/configuration/generator/PlatformGenerator.js new file mode 100644 index 0000000..b076193 --- /dev/null +++ b/modules/web-console/frontend/app/modules/configuration/generator/PlatformGenerator.js @@ -0,0 +1,522 @@ +/* + * 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 _ from 'lodash'; +import { EmptyBean, Bean } from './Beans'; + +export default ['JavaTypes', 'igniteClusterPlatformDefaults', 'igniteCachePlatformDefaults', (JavaTypes, clusterDflts, cacheDflts) => { + class PlatformGenerator { + static igniteConfigurationBean(cluster) { + return new Bean('Apache.Ignite.Core.IgniteConfiguration', 'cfg', cluster, clusterDflts); + } + + static cacheConfigurationBean(cache) { + return new Bean('Apache.Ignite.Core.Cache.Configuration.CacheConfiguration', 'ccfg', cache, cacheDflts); + } + + /** + * Function to generate ignite configuration. + * + * @param {Object} cluster Cluster to process. + * @return {String} Generated ignite configuration. + */ + static igniteConfiguration(cluster) { + const cfg = this.igniteConfigurationBean(cluster); + + this.clusterAtomics(cluster.atomics, cfg); + + return cfg; + } + + // Generate general section. + static clusterGeneral(cluster, cfg = this.igniteConfigurationBean(cluster)) { + cfg.stringProperty('name', 'GridName') + .stringProperty('localHost', 'Localhost'); + + if (_.isNil(cluster.discovery)) + return cfg; + + const discovery = new Bean('Apache.Ignite.Core.Discovery.Tcp.TcpDiscoverySpi', 'discovery', + cluster.discovery, clusterDflts.discovery); + + let ipFinder; + + switch (discovery.valueOf('kind')) { + case 'Vm': + ipFinder = new Bean('Apache.Ignite.Core.Discovery.Tcp.Static.TcpDiscoveryStaticIpFinder', + 'ipFinder', cluster.discovery.Vm, clusterDflts.discovery.Vm); + + ipFinder.collectionProperty('addrs', 'addresses', cluster.discovery.Vm.addresses, 'ICollection'); + + break; + case 'Multicast': + ipFinder = new Bean('Apache.Ignite.Core.Discovery.Tcp.Multicast.TcpDiscoveryMulticastIpFinder', + 'ipFinder', cluster.discovery.Multicast, clusterDflts.discovery.Multicast); + + ipFinder.stringProperty('MulticastGroup') + .intProperty('multicastPort', 'MulticastPort') + .intProperty('responseWaitTime', 'ResponseTimeout') + .intProperty('addressRequestAttempts', 'AddressRequestAttempts') + .stringProperty('localAddress', 'LocalAddress') + .collectionProperty('addrs', 'Endpoints', cluster.discovery.Multicast.addresses, 'ICollection'); + + break; + default: + } + + if (ipFinder) + discovery.beanProperty('IpFinder', ipFinder); + + cfg.beanProperty('DiscoverySpi', discovery); + + + return cfg; + } + + static clusterAtomics(atomics, cfg = this.igniteConfigurationBean()) { + const acfg = new Bean('Apache.Ignite.Core.DataStructures.Configuration.AtomicConfiguration', 'atomicCfg', + atomics, clusterDflts.atomics); + + acfg.enumProperty('cacheMode', 'CacheMode') + .intProperty('atomicSequenceReserveSize', 'AtomicSequenceReserveSize'); + + if (acfg.valueOf('cacheMode') === 'PARTITIONED') + acfg.intProperty('backups', 'Backups'); + + if (acfg.isEmpty()) + return cfg; + + cfg.beanProperty('AtomicConfiguration', acfg); + + return cfg; + } + + // Generate binary group. + static clusterBinary(binary, cfg = this.igniteConfigurationBean()) { + const binaryCfg = new Bean('Apache.Ignite.Core.Binary.BinaryConfiguration', 'binaryCfg', + binary, clusterDflts.binary); + + binaryCfg.emptyBeanProperty('idMapper', 'DefaultIdMapper') + .emptyBeanProperty('nameMapper', 'DefaultNameMapper') + .emptyBeanProperty('serializer', 'DefaultSerializer'); + + // const typeCfgs = []; + // + // _.forEach(binary.typeConfigurations, (type) => { + // const typeCfg = new MethodBean('Apache.Ignite.Core.Binary.BinaryTypeConfiguration', + // JavaTypes.toJavaName('binaryType', type.typeName), type, clusterDflts.binary.typeConfigurations); + // + // typeCfg.stringProperty('typeName', 'TypeName') + // .emptyBeanProperty('idMapper', 'IdMapper') + // .emptyBeanProperty('nameMapper', 'NameMapper') + // .emptyBeanProperty('serializer', 'Serializer') + // .intProperty('enum', 'IsEnum'); + // + // if (typeCfg.nonEmpty()) + // typeCfgs.push(typeCfg); + // }); + // + // binaryCfg.collectionProperty('types', 'TypeConfigurations', typeCfgs, 'ICollection', + // 'Apache.Ignite.Core.Binary.BinaryTypeConfiguration'); + // + // binaryCfg.boolProperty('compactFooter', 'CompactFooter'); + // + // if (binaryCfg.isEmpty()) + // return cfg; + // + // cfg.beanProperty('binaryConfiguration', binaryCfg); + + return cfg; + } + + // Generate communication group. + static clusterCommunication(cluster, cfg = this.igniteConfigurationBean(cluster)) { + const commSpi = new Bean('Apache.Ignite.Core.Communication.Tcp.TcpCommunicationSpi', 'communicationSpi', + cluster.communication, clusterDflts.communication); + + commSpi.emptyBeanProperty('listener') + .stringProperty('localAddress') + .intProperty('localPort') + .intProperty('localPortRange') + // .intProperty('sharedMemoryPort') + .intProperty('directBuffer') + .intProperty('directSendBuffer') + .intProperty('idleConnectionTimeout') + .intProperty('connectTimeout') + .intProperty('maxConnectTimeout') + .intProperty('reconnectCount') + .intProperty('socketSendBuffer') + .intProperty('socketReceiveBuffer') + .intProperty('messageQueueLimit') + .intProperty('slowClientQueueLimit') + .intProperty('tcpNoDelay') + .intProperty('ackSendThreshold') + .intProperty('unacknowledgedMessagesBufferSize') + // .intProperty('socketWriteTimeout') + .intProperty('selectorsCount'); + // .emptyBeanProperty('addressResolver'); + + if (commSpi.nonEmpty()) + cfg.beanProperty('CommunicationSpi', commSpi); + + cfg.intProperty('networkTimeout', 'NetworkTimeout') + .intProperty('networkSendRetryDelay') + .intProperty('networkSendRetryCount'); + // .intProperty('discoveryStartupDelay'); + + return cfg; + } + + // Generate discovery group. + static clusterDiscovery(discovery, cfg = this.igniteConfigurationBean()) { + if (discovery) { + let discoveryCfg = cfg.findProperty('discovery'); + + if (_.isNil(discoveryCfg)) { + discoveryCfg = new Bean('Apache.Ignite.Core.Discovery.Tcp.TcpDiscoverySpi', 'discovery', + discovery, clusterDflts.discovery); + } + + discoveryCfg.stringProperty('localAddress') + .intProperty('localPort') + .intProperty('localPortRange') + .intProperty('socketTimeout') + .intProperty('ackTimeout') + .intProperty('maxAckTimeout') + .intProperty('networkTimeout') + .intProperty('joinTimeout') + .intProperty('threadPriority') + .intProperty('heartbeatFrequency') + .intProperty('maxMissedHeartbeats') + .intProperty('maxMissedClientHeartbeats') + .intProperty('topHistorySize') + .intProperty('reconnectCount') + .intProperty('statisticsPrintFrequency') + .intProperty('ipFinderCleanFrequency') + .intProperty('forceServerMode') + .intProperty('clientReconnectDisabled'); + + if (discoveryCfg.nonEmpty()) + cfg.beanProperty('discoverySpi', discoveryCfg); + } + + return cfg; + } + + // Generate events group. + static clusterEvents(cluster, cfg = this.igniteConfigurationBean(cluster)) { + if (_.nonEmpty(cluster.includeEventTypes)) + cfg.eventTypes('events', 'includeEventTypes', cluster.includeEventTypes); + + return cfg; + } + + // Generate metrics group. + static clusterMetrics(cluster, cfg = this.igniteConfigurationBean(cluster)) { + cfg.intProperty('metricsExpireTime') + .intProperty('metricsHistorySize') + .intProperty('metricsLogFrequency') + .intProperty('metricsUpdateFrequency'); + + return cfg; + } + + // Generate transactions group. + static clusterTransactions(transactionConfiguration, cfg = this.igniteConfigurationBean()) { + const bean = new Bean('Apache.Ignite.Core.Transactions.TransactionConfiguration', 'TransactionConfiguration', + transactionConfiguration, clusterDflts.transactionConfiguration); + + bean.enumProperty('defaultTxConcurrency', 'DefaultTransactionConcurrency') + .enumProperty('defaultTxIsolation', 'DefaultTransactionIsolation') + .intProperty('defaultTxTimeout', 'DefaultTimeout') + .intProperty('pessimisticTxLogLinger', 'PessimisticTransactionLogLinger') + .intProperty('pessimisticTxLogSize', 'PessimisticTransactionLogSize'); + + if (bean.nonEmpty()) + cfg.beanProperty('transactionConfiguration', bean); + + return cfg; + } + + // Generate user attributes group. + static clusterUserAttributes(cluster, cfg = this.igniteConfigurationBean(cluster)) { + cfg.mapProperty('attributes', 'attributes', 'UserAttributes'); + + return cfg; + } + + static clusterCaches(cluster, caches, igfss, isSrvCfg, cfg = this.igniteConfigurationBean(cluster)) { + // const cfg = this.clusterGeneral(cluster, cfg); + // + // if (_.nonEmpty(caches)) { + // const ccfgs = _.map(caches, (cache) => this.cacheConfiguration(cache)); + // + // cfg.collectionProperty('', '', ccfgs, ); + // } + + return this.clusterGeneral(cluster, cfg); + } + + // Generate cache general group. + static cacheGeneral(cache, ccfg = this.cacheConfigurationBean(cache)) { + ccfg.stringProperty('name') + .enumProperty('cacheMode') + .enumProperty('atomicityMode'); + + if (ccfg.valueOf('cacheMode') === 'PARTITIONED' && ccfg.valueOf('backups')) { + ccfg.intProperty('backups') + .intProperty('readFromBackup'); + } + + ccfg.intProperty('copyOnRead'); + + if (ccfg.valueOf('cacheMode') === 'PARTITIONED' && ccfg.valueOf('atomicityMode') === 'TRANSACTIONAL') + ccfg.intProperty('invalidate'); + + return ccfg; + } + + // Generate cache memory group. + static cacheMemory(cache, ccfg = this.cacheConfigurationBean(cache)) { + ccfg.enumProperty('memoryMode'); + + if (ccfg.valueOf('memoryMode') !== 'OFFHEAP_VALUES') + ccfg.intProperty('offHeapMaxMemory'); + + // this._evictionPolicy(ccfg, 'evictionPolicy', cache.evictionPolicy, cacheDflts.evictionPolicy); + + ccfg.intProperty('startSize') + .boolProperty('swapEnabled', 'EnableSwap'); + + return ccfg; + } + + // Generate cache queries & Indexing group. + static cacheQuery(cache, domains, ccfg = this.cacheConfigurationBean(cache)) { + ccfg.intProperty('sqlOnheapRowCacheSize') + .intProperty('longQueryWarningTimeout'); + + return ccfg; + } + + // Generate cache store group. + static cacheStore(cache, domains, 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); + + const id = bean.valueOf('dataSourceBean'); + + bean.dataSource(id, 'dataSourceBean', this.dataSourceBean(id, storeFactory.dialect)) + .beanProperty('dialect', new EmptyBean(this.dialectClsName(storeFactory.dialect))); + + 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 = new Bean('org.apache.ignite.cache.store.jdbc.JdbcType', 'type', + _.merge({}, domain, {cacheName: cache.name})) + .stringProperty('cacheName'); + + setType(typeBean, 'keyType'); + setType(typeBean, 'valueType'); + + this.domainStore(domain, typeBean); + + acc.push(typeBean); + + return acc; + }, []); + + bean.arrayProperty('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') + bean.dataSource(bean.valueOf('dataSourceBean'), 'dataSourceBean', this.dialectClsName(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') + .intProperty('writeBehindFlushFrequency') + .intProperty('writeBehindFlushThreadCount'); + } + + return ccfg; + } + + // Generate cache concurrency control group. + static cacheConcurrency(cache, ccfg = this.cacheConfigurationBean(cache)) { + ccfg.intProperty('maxConcurrentAsyncOperations') + .intProperty('defaultLockTimeout') + .enumProperty('atomicWriteOrderMode') + .enumProperty('writeSynchronizationMode'); + + return ccfg; + } + + // Generate cache node filter group. + static cacheNodeFilter(cache, igfss, ccfg = this.cacheConfigurationBean(cache)) { + const kind = _.get(cache, 'nodeFilter.kind'); + + if (kind && cache.nodeFilter[kind]) { + let bean = null; + + switch (kind) { + case 'IGFS': + const foundIgfs = _.find(igfss, (igfs) => igfs._id === cache.nodeFilter.IGFS.igfs); + + if (foundIgfs) { + bean = new Bean('org.apache.ignite.internal.processors.igfs.IgfsNodePredicate', 'nodeFilter', foundIgfs) + .stringConstructorArgument('name'); + } + + break; + case 'Custom': + bean = new Bean(cache.nodeFilter.Custom.className, 'nodeFilter'); + + break; + default: + return ccfg; + } + + if (bean) + ccfg.beanProperty('nodeFilter', bean); + } + + 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') + .intProperty('rebalanceBatchesPrefetchCount') + .intProperty('rebalanceOrder') + .intProperty('rebalanceDelay') + .intProperty('rebalanceTimeout') + .intProperty('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 cacheServerNearCache(cache, ccfg = this.cacheConfigurationBean(cache)) { + if (cache.cacheMode === 'PARTITIONED' && cache.nearCacheEnabled) { + const bean = new Bean('org.apache.ignite.configuration.NearCacheConfiguration', 'nearConfiguration', + cache.nearConfiguration, {nearStartSize: 375000}); + + bean.intProperty('nearStartSize'); + + this._evictionPolicy(bean, 'nearEvictionPolicy', + bean.valueOf('nearEvictionPolicy'), cacheDflts.evictionPolicy); + + ccfg.beanProperty('nearConfiguration', bean); + } + + return ccfg; + } + + // Generate cache statistics group. + static cacheStatistics(cache, ccfg = this.cacheConfigurationBean(cache)) { + ccfg.boolProperty('statisticsEnabled') + .boolProperty('managementEnabled'); + + return ccfg; + } + + static cacheConfiguration(cache, ccfg = this.cacheConfigurationBean(cache)) { + this.cacheGeneral(cache, ccfg); + this.cacheMemory(cache, ccfg); + this.cacheQuery(cache, cache.domains, ccfg); + this.cacheStore(cache, cache.domains, ccfg); + + const igfs = _.get(cache, 'nodeFilter.IGFS.instance'); + this.cacheNodeFilter(cache, igfs ? [igfs] : [], ccfg); + this.cacheConcurrency(cache, ccfg); + this.cacheRebalance(cache, ccfg); + this.cacheServerNearCache(cache, ccfg); + this.cacheStatistics(cache, ccfg); + // this.cacheDomains(cache.domains, cfg); + + return ccfg; + } + } + + return PlatformGenerator; +}];
