modules/frontend/app/services/LegacyUtils.service.js (417 lines of code) (raw):

/* * 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 saver from 'file-saver'; import _ from 'lodash'; // TODO: Refactor this service for legacy tables with more than one input field. /** * @param {import('./ErrorPopover.service').default} ErrorPopover */ export default function service(ErrorPopover) { function isDefined(v) { return !_.isNil(v); } function isEmptyString(s) { if (isDefined(s)) return s.trim().length === 0; return true; } const javaBuiltInClasses = [ 'BigDecimal', 'Boolean', 'Byte', 'Date', 'Double', 'Float', 'Integer', 'Long', 'Object', 'Short', 'String', 'Time', 'Timestamp', 'UUID' ]; const javaBuiltInTypes = [ 'BigDecimal', 'boolean', 'Boolean', 'byte', 'byte[]', 'Byte', 'Date', 'double', 'Double', 'float', 'Float', 'int', 'Integer', 'long', 'Long', 'Object', 'short', 'Short', 'String', 'Time', 'Timestamp', 'UUID' ]; const javaBuiltInFullNameClasses = [ 'java.math.BigDecimal', 'java.lang.Boolean', 'java.lang.Byte', 'java.sql.Date', 'java.lang.Double', 'java.lang.Float', 'java.lang.Integer', 'java.lang.Long', 'java.lang.Object', 'java.lang.Short', 'java.lang.String', 'java.sql.Time', 'java.sql.Timestamp', 'java.util.UUID' ]; /** * @param clsName Class name to check. * @param additionalClasses List of classes to check as builtin. * @returns {Boolean} 'true' if given class name is a java build-in type. */ function isJavaBuiltInClass(clsName, additionalClasses) { if (isEmptyString(clsName)) return false; return _.includes(javaBuiltInClasses, clsName) || _.includes(javaBuiltInFullNameClasses, clsName) || (_.isArray(additionalClasses) && _.includes(additionalClasses, clsName)); } const SUPPORTED_JDBC_TYPES = [ 'BIGINT', 'BIT', 'BOOLEAN', 'BLOB', 'CHAR', 'CLOB', 'DATE', 'DECIMAL', 'DOUBLE', 'FLOAT', 'INTEGER', 'LONGNVARCHAR', 'LONGVARCHAR', 'NCHAR', 'NUMERIC', 'NVARCHAR', 'REAL', 'SMALLINT', 'TIME', 'TIMESTAMP', 'TINYINT', 'VARCHAR' ]; /*eslint-disable */ const JAVA_KEYWORDS = [ 'abstract', 'assert', 'boolean', 'break', 'byte', 'case', 'catch', 'char', 'class', 'const', 'continue', 'default', 'do', 'double', 'else', 'enum', 'extends', 'false', 'final', 'finally', 'float', 'for', 'goto', 'if', 'implements', 'import', 'instanceof', 'int', 'interface', 'long', 'native', 'new', 'null', 'package', 'private', 'protected', 'public', 'return', 'short', 'static', 'strictfp', 'super', 'switch', 'synchronized', 'this', 'throw', 'throws', 'transient', 'true', 'try', 'void', 'volatile', 'while' ]; /* eslint-enable */ const VALID_JAVA_IDENTIFIER = new RegExp('^[a-zA-Z_$][a-zA-Z\\d_$]*$'); function isValidJavaIdentifier(msg, ident, elemId, panels, panelId, stopEdit) { if (isEmptyString(ident)) return !stopEdit && ErrorPopover.show(elemId, msg + ' is invalid!', panels, panelId); if (_.includes(JAVA_KEYWORDS, ident)) return !stopEdit && ErrorPopover.show(elemId, msg + ' could not contains reserved java keyword: "' + ident + '"!', panels, panelId); if (!VALID_JAVA_IDENTIFIER.test(ident)) return !stopEdit && ErrorPopover.show(elemId, msg + ' contains invalid identifier: "' + ident + '"!', panels, panelId); return true; } /** * Extract datasource from cache or cluster. * * @param object Cache or cluster to extract datasource. * @returns {*} Datasource object or null if not set. */ function extractDataSource(object) { let datasource = null; // Extract from cluster object if (_.get(object, 'discovery.kind') === 'Jdbc') { datasource = object.discovery.Jdbc; if (datasource.dataSourceBean && datasource.dialect) return datasource; } // Extract from JDBC checkpoint configuration. else if (_.get(object, 'kind') === 'JDBC') { datasource = object.JDBC; if (datasource.dataSourceBean && datasource.dialect) return datasource; } // Extract from cache object else if (_.get(object, 'cacheStoreFactory.kind')) { datasource = object.cacheStoreFactory[object.cacheStoreFactory.kind]; if (datasource.dialect || (datasource.connectVia === 'DataSource')) return datasource; } return null; } const cacheStoreJdbcDialects = [ {value: 'Generic', label: 'Generic JDBC'}, {value: 'Oracle', label: 'Oracle'}, {value: 'DB2', label: 'IBM DB2'}, {value: 'SQLServer', label: 'Microsoft SQL Server'}, {value: 'MySQL', label: 'MySQL'}, {value: 'PostgreSQL', label: 'PostgreSQL'}, {value: 'H2', label: 'H2 database'} ]; function domainForStoreConfigured(domain) { const isEmpty = !isDefined(domain) || (isEmptyString(domain.databaseSchema) && isEmptyString(domain.databaseTable) && _.isEmpty(domain.keyFields) && _.isEmpty(domain.valueFields)); return !isEmpty; } const DS_CHECK_SUCCESS = {checked: true}; /** * Compare datasources of caches or clusters. * * @param firstObj First cache or cluster. * @param firstType Type of first object to compare. * @param secondObj Second cache or cluster. * @param secondType Type of first object to compare. * @param index Index of invalid object when check is failed. * @returns {*} Check result object. */ function compareDataSources(firstObj, firstType, secondObj, secondType, index) { const firstDs = extractDataSource(firstObj); const secondDs = extractDataSource(secondObj); if (firstDs && secondDs) { const firstDB = firstDs.dialect; const secondDB = secondDs.dialect; if (firstDs.dataSourceBean === secondDs.dataSourceBean && firstDB !== secondDB) return {checked: false, firstObj, firstDs, firstType, secondObj, secondDs, secondType, index}; } return DS_CHECK_SUCCESS; } function compareSQLSchemaNames(firstCache, secondCache) { const firstName = firstCache.sqlSchema; const secondName = secondCache.sqlSchema; if (firstName && secondName && (firstName === secondName)) return {checked: false, firstCache, secondCache}; return DS_CHECK_SUCCESS; } function toJavaName(prefix, name) { const javaName = name ? name.replace(/[^A-Za-z_0-9]+/g, '_') : 'dflt'; return prefix + javaName.charAt(0).toLocaleUpperCase() + javaName.slice(1); } return { VALID_JAVA_IDENTIFIER, JAVA_KEYWORDS, mkOptions(options) { return _.map(options, (option) => { return {value: option, label: isDefined(option) ? option : 'Not set'}; }); }, isDefined, hasProperty(obj, props) { for (const propName in props) { if (props.hasOwnProperty(propName)) { if (obj[propName]) return true; } } return false; }, isEmptyString, SUPPORTED_JDBC_TYPES, javaBuiltInClasses, javaBuiltInTypes, isJavaBuiltInClass, isValidJavaIdentifier, isValidJavaClass(msg, ident, allowBuiltInClass, elemId, packageOnly, panels, panelId, stopEdit = false) { if (isEmptyString(ident)) return !stopEdit && ErrorPopover.show(elemId, msg + ' could not be empty!', panels, panelId); const parts = ident.split('.'); const len = parts.length; if (!allowBuiltInClass && isJavaBuiltInClass(ident)) return !stopEdit && ErrorPopover.show(elemId, msg + ' should not be the Java build-in class!', panels, panelId); if (len < 2) { if (isJavaBuiltInClass(ident, allowBuiltInClass)) return true; if (!packageOnly) return !stopEdit && ErrorPopover.show(elemId, msg + ' does not have package specified!', panels, panelId); } for (let i = 0; i < parts.length; i++) { const part = parts[i]; if (!isValidJavaIdentifier(msg, part, elemId, panels, panelId, stopEdit)) return false; } return true; }, domainForQueryConfigured(domain) { const isEmpty = !isDefined(domain) || (_.isEmpty(domain.fields) && _.isEmpty(domain.aliases) && _.isEmpty(domain.indexes)); return !isEmpty; }, domainForStoreConfigured, download(type = 'application/octet-stream', name = 'file.txt', data = '') { const file = new Blob([data], { type: `${type};charset=utf-8`}); saver.saveAs(file, name, false); }, getQueryVariable(name) { const attrs = window.location.search.substring(1).split('&'); const attr = _.find(attrs, (a) => a === name || (a.indexOf('=') >= 0 && a.substr(0, a.indexOf('=')) === name)); if (!isDefined(attr)) return null; if (attr === name) return true; return attr.substr(attr.indexOf('=') + 1); }, cacheStoreJdbcDialects, cacheStoreJdbcDialectsLabel(dialect) { const found = _.find(cacheStoreJdbcDialects, (dialectVal) => dialectVal.value === dialect); return found ? found.label : null; }, checkDataSources(cluster, caches, checkCacheExt) { let res = DS_CHECK_SUCCESS; _.find(caches, (curCache, curIx) => { // Check datasources of cluster JDBC ip finder and cache store factory datasource. res = compareDataSources(curCache, 'cache', cluster, 'cluster'); if (!res.checked) return true; _.find(cluster.checkpointSpi, (spi, spiIx) => { res = compareDataSources(curCache, 'cache', spi, 'checkpoint', spiIx); return !res.checked; }); if (!res.checked) return true; // Check datasource of current saved cache and datasource of other cache in cluster. if (isDefined(checkCacheExt)) { if (checkCacheExt._id !== curCache._id) { res = compareDataSources(checkCacheExt, 'cache', curCache, 'cache'); return !res.checked; } return false; } // Check datasources of specified list of caches. return _.find(caches, (checkCache, checkIx) => { if (checkIx < curIx) { res = compareDataSources(checkCache, 'cache', curCache, 'cache'); return !res.checked; } return false; }); }); if (res.checked) { _.find(cluster.checkpointSpi, (curSpi, curIx) => { // Check datasources of cluster JDBC ip finder and cache store factory datasource. res = compareDataSources(cluster, 'cluster', curSpi, 'checkpoint', curIx); if (!res.checked) return true; _.find(cluster.checkpointSpi, (spi, spiIx) => { if (spiIx < curIx) { res = compareDataSources(curSpi, 'checkpoint', spi, 'checkpoint', curIx); return !res.checked; } return false; }); }); } return res; }, checkCacheSQLSchemas(caches, checkCacheExt) { let res = DS_CHECK_SUCCESS; _.find(caches, (curCache, curIx) => { if (isDefined(checkCacheExt)) { if (checkCacheExt._id !== curCache._id) { res = compareSQLSchemaNames(checkCacheExt, curCache); return !res.checked; } return false; } return _.find(caches, (checkCache, checkIx) => { if (checkIx < curIx) { res = compareSQLSchemaNames(checkCache, curCache); return !res.checked; } return false; }); }); return res; }, autoCacheStoreConfiguration(cache, domains) { const cacheStoreFactory = isDefined(cache.cacheStoreFactory) && isDefined(cache.cacheStoreFactory.kind); if (!cacheStoreFactory && _.findIndex(domains, domainForStoreConfigured) >= 0) { const dflt = !cache.readThrough && !cache.writeThrough; return { cacheStoreFactory: { kind: 'CacheJdbcPojoStoreFactory', CacheJdbcPojoStoreFactory: { dataSourceBean: toJavaName('ds', cache.name), dialect: 'Generic' }, CacheJdbcBlobStoreFactory: {connectVia: 'DataSource'} }, readThrough: dflt || cache.readThrough, writeThrough: dflt || cache.writeThrough }; } return {}; }, randomString(len) { const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; const possibleLen = possible.length; let res = ''; for (let i = 0; i < len; i++) res += possible.charAt(Math.floor(Math.random() * possibleLen)); return res; }, checkFieldValidators(ui) { const form = ui.inputForm; const errors = form.$error; const errKeys = Object.keys(errors); if (errKeys && errKeys.length > 0) { const firstErrorKey = errKeys[0]; const firstError = errors[firstErrorKey][0]; const err = firstError.$error[firstErrorKey]; const actualError = _.isArray(err) ? err[0] : firstError; const errNameFull = actualError.$name; const errNameShort = errNameFull.endsWith('TextInput') ? errNameFull.substring(0, errNameFull.length - 9) : errNameFull; const extractErrorMessage = (errName) => { try { return errors[firstErrorKey][0].$errorMessages[errName][firstErrorKey]; } catch (ignored1) { try { return form[firstError.$name].$errorMessages[errName][firstErrorKey]; } catch (ignored2) { try { return form.$errorMessages[errName][firstErrorKey]; } catch (ignored3) { return false; } } } }; const msg = extractErrorMessage(errNameFull) || extractErrorMessage(errNameShort) || 'Invalid value!'; return ErrorPopover.show(errNameFull, msg, ui, firstError.$name); } return true; } }; } service.$inject = ['IgniteErrorPopover'];