blob: 8362669af088b57f38762795718830a76e075806 [file] [log] [blame]
/*
* 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'];