| /* |
| * 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 templateUrl from './template.tpl.pug'; |
| import './style.scss'; |
| import _ from 'lodash'; |
| import naturalCompare from 'natural-compare-lite'; |
| import find from 'lodash/fp/find'; |
| import get from 'lodash/fp/get'; |
| import {combineLatest, EMPTY, from, merge, of, race, timer} from 'rxjs'; |
| import {distinctUntilChanged, filter, map, pluck, switchMap, take, tap} from 'rxjs/operators'; |
| import ObjectID from 'bson-objectid'; |
| import {uniqueName} from 'app/utils/uniqueName'; |
| import {defaultNames} from '../../defaultNames'; |
| // eslint-disable-next-line |
| import {UIRouter} from '@uirouter/angularjs' |
| import {default as IgniteConfirmBatch} from 'app/services/ConfirmBatch.service'; |
| import {default as ConfigSelectors} from '../../store/selectors'; |
| import {default as ConfigEffects} from '../../store/effects'; |
| import {default as ConfigureState} from '../../services/ConfigureState'; |
| // eslint-disable-next-line |
| import {default as AgentManager} from 'app/modules/agent/AgentModal.service' |
| import {default as SqlTypes} from 'app/services/SqlTypes.service'; |
| import {default as JavaTypes} from 'app/services/JavaTypes.service'; |
| // eslint-disable-next-line |
| import {default as ActivitiesData} from 'app/core/activities/Activities.data'; |
| |
| function _mapCaches(caches = []) { |
| return caches.map((cache) => { |
| return {label: cache.name, value: cache._id, cache}; |
| }); |
| } |
| |
| const INFO_CONNECT_TO_DB = 'Configure connection to database'; |
| const INFO_SELECT_SCHEMAS = 'Select schemas to load tables from'; |
| const INFO_SELECT_TABLES = 'Select tables to import as domain model'; |
| const INFO_SELECT_OPTIONS = 'Select import domain model options'; |
| const LOADING_JDBC_DRIVERS = {text: 'Loading JDBC drivers...'}; |
| const LOADING_SCHEMAS = {text: 'Loading schemas...'}; |
| const LOADING_TABLES = {text: 'Loading tables...'}; |
| const SAVING_DOMAINS = {text: 'Saving domain model...'}; |
| |
| const IMPORT_DM_NEW_CACHE = 1; |
| const IMPORT_DM_ASSOCIATE_CACHE = 2; |
| |
| const DFLT_PARTITIONED_CACHE = { |
| label: 'PARTITIONED', |
| value: -1, |
| cache: { |
| name: 'PARTITIONED', |
| cacheMode: 'PARTITIONED', |
| atomicityMode: 'ATOMIC', |
| readThrough: true, |
| writeThrough: true |
| } |
| }; |
| |
| const DFLT_REPLICATED_CACHE = { |
| label: 'REPLICATED', |
| value: -2, |
| cache: { |
| name: 'REPLICATED', |
| cacheMode: 'REPLICATED', |
| atomicityMode: 'ATOMIC', |
| readThrough: true, |
| writeThrough: true |
| } |
| }; |
| |
| const CACHE_TEMPLATES = [DFLT_PARTITIONED_CACHE, DFLT_REPLICATED_CACHE]; |
| |
| export class ModalImportModels { |
| /** |
| * Cluster ID to import models into |
| * @type {string} |
| */ |
| clusterID; |
| |
| /** @type {ng.ICompiledExpression} */ |
| onHide; |
| |
| static $inject = ['$uiRouter', 'ConfigSelectors', 'ConfigEffects', 'ConfigureState', 'IgniteConfirm', 'IgniteConfirmBatch', 'IgniteFocus', 'SqlTypes', 'JavaTypes', 'IgniteMessages', '$scope', '$rootScope', 'AgentManager', 'IgniteActivitiesData', 'IgniteLoading', 'IgniteFormUtils', 'IgniteLegacyUtils']; |
| |
| /** |
| * @param {UIRouter} $uiRouter |
| * @param {ConfigSelectors} ConfigSelectors |
| * @param {ConfigEffects} ConfigEffects |
| * @param {ConfigureState} ConfigureState |
| * @param {IgniteConfirmBatch} ConfirmBatch |
| * @param {SqlTypes} SqlTypes |
| * @param {JavaTypes} JavaTypes |
| * @param {ng.IScope} $scope |
| * @param {ng.IRootScopeService} $root |
| * @param {AgentManager} agentMgr |
| * @param {ActivitiesData} ActivitiesData |
| */ |
| constructor($uiRouter, ConfigSelectors, ConfigEffects, ConfigureState, Confirm, ConfirmBatch, Focus, SqlTypes, JavaTypes, Messages, $scope, $root, agentMgr, ActivitiesData, Loading, FormUtils, LegacyUtils) { |
| this.$uiRouter = $uiRouter; |
| this.ConfirmBatch = ConfirmBatch; |
| this.ConfigSelectors = ConfigSelectors; |
| this.ConfigEffects = ConfigEffects; |
| this.ConfigureState = ConfigureState; |
| this.$root = $root; |
| this.$scope = $scope; |
| this.agentMgr = agentMgr; |
| this.JavaTypes = JavaTypes; |
| this.SqlTypes = SqlTypes; |
| this.ActivitiesData = ActivitiesData; |
| Object.assign(this, {Confirm, Focus, Messages, Loading, FormUtils, LegacyUtils}); |
| } |
| |
| loadData() { |
| return of(this.clusterID).pipe( |
| switchMap((id = 'new') => { |
| return this.ConfigureState.state$.pipe(this.ConfigSelectors.selectClusterToEdit(id, defaultNames.importedCluster)); |
| }), |
| switchMap((cluster) => { |
| return (!(cluster.caches || []).length && !(cluster.models || []).length) |
| ? of({ |
| cluster, |
| caches: [], |
| models: [] |
| }) |
| : from(Promise.all([ |
| this.ConfigEffects.etp('LOAD_SHORT_CACHES', {ids: cluster.caches || [], clusterID: cluster._id}), |
| this.ConfigEffects.etp('LOAD_SHORT_MODELS', {ids: cluster.models || [], clusterID: cluster._id}) |
| ])).pipe(switchMap(() => { |
| return combineLatest( |
| this.ConfigureState.state$.pipe(this.ConfigSelectors.selectShortCachesValue()), |
| this.ConfigureState.state$.pipe(this.ConfigSelectors.selectShortModelsValue()), |
| (caches, models) => ({ |
| cluster, |
| caches, |
| models |
| }) |
| ).pipe(take(1)); |
| })); |
| }), |
| take(1) |
| ); |
| } |
| |
| saveBatch(batch) { |
| if (!batch.length) |
| return; |
| |
| this.$scope.importDomain.loadingOptions = SAVING_DOMAINS; |
| this.Loading.start('importDomainFromDb'); |
| |
| this.ConfigureState.dispatchAction({ |
| type: 'ADVANCED_SAVE_COMPLETE_CONFIGURATION', |
| changedItems: this.batchActionsToRequestBody(batch), |
| prevActions: [] |
| }); |
| |
| this.saveSubscription = race( |
| this.ConfigureState.actions$.pipe( |
| filter((a) => a.type === 'ADVANCED_SAVE_COMPLETE_CONFIGURATION_OK'), |
| tap(() => this.onHide()) |
| ), |
| this.ConfigureState.actions$.pipe( |
| filter((a) => a.type === 'ADVANCED_SAVE_COMPLETE_CONFIGURATION_ERR') |
| ) |
| ).pipe( |
| take(1), |
| tap(() => { |
| this.Loading.finish('importDomainFromDb'); |
| }) |
| ) |
| .subscribe(); |
| } |
| |
| batchActionsToRequestBody(batch) { |
| const result = batch.reduce((req, action) => { |
| return { |
| ...req, |
| cluster: { |
| ...req.cluster, |
| models: [...req.cluster.models, action.newDomainModel._id], |
| caches: [...req.cluster.caches, ...action.newDomainModel.caches] |
| }, |
| models: [...req.models, action.newDomainModel], |
| caches: action.newCache |
| ? [...req.caches, action.newCache] |
| : action.cacheStoreChanges |
| ? [...req.caches, { |
| ...this.loadedCaches[action.cacheStoreChanges[0].cacheId], |
| ...action.cacheStoreChanges[0].change |
| }] |
| : req.caches |
| }; |
| }, {cluster: this.cluster, models: [], caches: [], igfss: []}); |
| result.cluster.models = [...new Set(result.cluster.models)]; |
| result.cluster.caches = [...new Set(result.cluster.caches)]; |
| return result; |
| } |
| |
| onTableSelectionChange(selected) { |
| this.$scope.$applyAsync(() => { |
| this.$scope.importDomain.tablesToUse = selected; |
| this.selectedTablesIDs = selected.map((t) => t.id); |
| }); |
| } |
| |
| onSchemaSelectionChange(selected) { |
| this.$scope.$applyAsync(() => { |
| this.$scope.importDomain.schemasToUse = selected; |
| this.selectedSchemasIDs = selected.map((i) => i.name); |
| }); |
| } |
| |
| onVisibleRowsChange(rows) { |
| return this.visibleTables = rows.map((r) => r.entity); |
| } |
| |
| onCacheSelect(cacheID) { |
| if (cacheID < 0) |
| return; |
| |
| if (this.loadedCaches[cacheID]) |
| return; |
| |
| return this.onCacheSelectSubcription = merge( |
| timer(0, 1).pipe( |
| take(1), |
| tap(() => this.ConfigureState.dispatchAction({type: 'LOAD_CACHE', cacheID})) |
| ), |
| race( |
| this.ConfigureState.actions$.pipe( |
| filter((a) => a.type === 'LOAD_CACHE_OK' && a.cache._id === cacheID), |
| pluck('cache'), |
| tap((cache) => { |
| this.loadedCaches[cacheID] = cache; |
| }) |
| ), |
| this.ConfigureState.actions$.pipe( |
| filter((a) => a.type === 'LOAD_CACHE_ERR' && a.action.cacheID === cacheID) |
| ) |
| ).pipe(take(1)) |
| ) |
| .subscribe(); |
| } |
| |
| $onDestroy() { |
| this.subscribers$.unsubscribe(); |
| if (this.onCacheSelectSubcription) this.onCacheSelectSubcription.unsubscribe(); |
| if (this.saveSubscription) this.saveSubscription.unsubscribe(); |
| } |
| |
| $onInit() { |
| // Restores old behavior |
| const {Confirm, ConfirmBatch, Focus, SqlTypes, JavaTypes, Messages, $scope, $root, agentMgr, ActivitiesData, Loading, FormUtils, LegacyUtils} = this; |
| |
| /** |
| * Convert some name to valid java package name. |
| * |
| * @param name to convert. |
| * @returns {string} Valid java package name. |
| */ |
| const _toJavaPackage = (name) => { |
| return name ? name.replace(/[^A-Za-z_0-9/.]+/g, '_') : 'org'; |
| }; |
| |
| const importDomainModal = { |
| hide: () => { |
| agentMgr.stopWatch(); |
| this.onHide(); |
| } |
| }; |
| |
| const _makeDefaultPackageName = (user) => user |
| ? _toJavaPackage(`${user.email.replace('@', '.').split('.').reverse().join('.')}.model`) |
| : void 0; |
| |
| this.$scope.ui = { |
| generatePojo: true, |
| builtinKeys: true, |
| generateKeyFields: true, |
| usePrimitives: true, |
| generateTypeAliases: true, |
| generateFieldAliases: true, |
| packageNameUserInput: _makeDefaultPackageName($root.user) |
| }; |
| this.$scope.$hide = importDomainModal.hide; |
| |
| this.$scope.importCommon = {}; |
| |
| this.subscription = this.loadData().pipe(tap((data) => { |
| this.$scope.caches = _mapCaches(data.caches); |
| this.$scope.domains = data.models; |
| this.caches = data.caches; |
| this.cluster = data.cluster; |
| |
| if (!_.isEmpty(this.$scope.caches)) { |
| this.$scope.importActions.push({ |
| label: 'Associate with existing cache', |
| shortLabel: 'Associate', |
| value: IMPORT_DM_ASSOCIATE_CACHE |
| }); |
| } |
| this.$scope.$watch('importCommon.action', this._fillCommonCachesOrTemplates(this.$scope.importCommon), true); |
| this.$scope.importCommon.action = IMPORT_DM_NEW_CACHE; |
| })); |
| |
| // New |
| this.loadedCaches = { |
| ...CACHE_TEMPLATES.reduce((a, c) => ({...a, [c.value]: c.cache}), {}) |
| }; |
| |
| this.actions = [ |
| {value: 'connect', label: this.$root.IgniteDemoMode ? 'Description' : 'Connection'}, |
| {value: 'schemas', label: 'Schemas'}, |
| {value: 'tables', label: 'Tables'}, |
| {value: 'options', label: 'Options'} |
| ]; |
| |
| // Legacy |
| $scope.ui.invalidKeyFieldsTooltip = 'Found key types without configured key fields<br/>' + |
| 'It may be a result of import tables from database without primary keys<br/>' + |
| 'Key field for such key types should be configured manually'; |
| |
| $scope.indexType = LegacyUtils.mkOptions(['SORTED', 'FULLTEXT', 'GEOSPATIAL']); |
| |
| $scope.importActions = [{ |
| label: 'Create new cache by template', |
| shortLabel: 'Create', |
| value: IMPORT_DM_NEW_CACHE |
| }]; |
| |
| |
| const _dbPresets = [ |
| { |
| db: 'Oracle', |
| jdbcDriverClass: 'oracle.jdbc.OracleDriver', |
| jdbcUrl: 'jdbc:oracle:thin:@[host]:[port]:[database]', |
| user: 'system' |
| }, |
| { |
| db: 'DB2', |
| jdbcDriverClass: 'com.ibm.db2.jcc.DB2Driver', |
| jdbcUrl: 'jdbc:db2://[host]:[port]/[database]', |
| user: 'db2admin' |
| }, |
| { |
| db: 'SQLServer', |
| jdbcDriverClass: 'com.microsoft.sqlserver.jdbc.SQLServerDriver', |
| jdbcUrl: 'jdbc:sqlserver://[host]:[port][;databaseName=database]' |
| }, |
| { |
| db: 'PostgreSQL', |
| jdbcDriverClass: 'org.postgresql.Driver', |
| jdbcUrl: 'jdbc:postgresql://[host]:[port]/[database]', |
| user: 'sa' |
| }, |
| { |
| db: 'MySQL', |
| jdbcDriverClass: 'com.mysql.jdbc.Driver', |
| jdbcUrl: 'jdbc:mysql://[host]:[port]/[database]', |
| user: 'root' |
| }, |
| { |
| db: 'MySQL', |
| jdbcDriverClass: 'com.mysql.cj.jdbc.Driver', |
| jdbcUrl: 'jdbc:mysql://[host]:[port]/[database]', |
| user: 'root' |
| }, |
| { |
| db: 'MySQL', |
| jdbcDriverClass: 'org.mariadb.jdbc.Driver', |
| jdbcUrl: 'jdbc:mariadb://[host]:[port]/[database]', |
| user: 'root' |
| }, |
| { |
| db: 'H2', |
| jdbcDriverClass: 'org.h2.Driver', |
| jdbcUrl: 'jdbc:h2:tcp://[host]/[database]', |
| user: 'sa' |
| } |
| ]; |
| |
| $scope.selectedPreset = { |
| db: 'Generic', |
| jdbcDriverJar: '', |
| jdbcDriverClass: '', |
| jdbcUrl: 'jdbc:[database]', |
| user: 'sa', |
| password: '', |
| tablesOnly: true |
| }; |
| |
| $scope.demoConnection = { |
| db: 'H2', |
| jdbcDriverClass: 'org.h2.Driver', |
| jdbcUrl: 'jdbc:h2:mem:demo-db', |
| user: 'sa', |
| password: '', |
| tablesOnly: true |
| }; |
| |
| function _loadPresets() { |
| try { |
| const restoredPresets = JSON.parse(localStorage.dbPresets); |
| |
| _.forEach(restoredPresets, (restoredPreset) => { |
| const preset = _.find(_dbPresets, {jdbcDriverClass: restoredPreset.jdbcDriverClass}); |
| |
| if (preset) { |
| preset.jdbcUrl = restoredPreset.jdbcUrl; |
| preset.user = restoredPreset.user; |
| } |
| }); |
| } |
| catch (ignore) { |
| // No-op. |
| } |
| } |
| |
| _loadPresets(); |
| |
| function _savePreset(preset) { |
| try { |
| const oldPreset = _.find(_dbPresets, {jdbcDriverClass: preset.jdbcDriverClass}); |
| |
| if (oldPreset) |
| _.assign(oldPreset, preset); |
| else |
| _dbPresets.push(preset); |
| |
| localStorage.dbPresets = JSON.stringify(_dbPresets); |
| } |
| catch (err) { |
| Messages.showError(err); |
| } |
| } |
| |
| function _findPreset(selectedJdbcJar) { |
| let result = _.find(_dbPresets, function(preset) { |
| return preset.jdbcDriverClass === selectedJdbcJar.jdbcDriverClass; |
| }); |
| |
| if (!result) |
| result = {db: 'Generic', jdbcUrl: 'jdbc:[database]', user: 'admin'}; |
| |
| result.jdbcDriverJar = selectedJdbcJar.jdbcDriverJar; |
| result.jdbcDriverClass = selectedJdbcJar.jdbcDriverClass; |
| result.jdbcDriverImplementationVersion = selectedJdbcJar.jdbcDriverImplementationVersion; |
| |
| return result; |
| } |
| |
| function isValidJavaIdentifier(s) { |
| return JavaTypes.validIdentifier(s) && !JavaTypes.isKeyword(s) && JavaTypes.nonBuiltInClass(s) && |
| SqlTypes.validIdentifier(s) && !SqlTypes.isKeyword(s); |
| } |
| |
| function toJavaIdentifier(name) { |
| if (_.isEmpty(name)) |
| return 'DB'; |
| |
| const len = name.length; |
| |
| let ident = ''; |
| |
| let capitalizeNext = true; |
| |
| for (let i = 0; i < len; i++) { |
| const ch = name.charAt(i); |
| |
| if (ch === ' ' || ch === '_') |
| capitalizeNext = true; |
| else if (ch === '-') { |
| ident += '_'; |
| capitalizeNext = true; |
| } |
| else if (capitalizeNext) { |
| ident += ch.toLocaleUpperCase(); |
| |
| capitalizeNext = false; |
| } |
| else |
| ident += ch.toLocaleLowerCase(); |
| } |
| |
| return ident; |
| } |
| |
| function toJavaClassName(name) { |
| const clazzName = toJavaIdentifier(name); |
| |
| if (isValidJavaIdentifier(clazzName)) |
| return clazzName; |
| |
| return 'Class' + clazzName; |
| } |
| |
| function toJavaFieldName(dbName) { |
| const javaName = toJavaIdentifier(dbName); |
| |
| const fieldName = javaName.charAt(0).toLocaleLowerCase() + javaName.slice(1); |
| |
| if (isValidJavaIdentifier(fieldName)) |
| return fieldName; |
| |
| return 'field' + javaName; |
| } |
| |
| /** |
| * Load list of database schemas. |
| */ |
| const _loadSchemas = () => { |
| agentMgr.awaitAgent() |
| .then(function() { |
| $scope.importDomain.loadingOptions = LOADING_SCHEMAS; |
| Loading.start('importDomainFromDb'); |
| |
| if ($root.IgniteDemoMode) |
| return agentMgr.schemas($scope.demoConnection); |
| |
| const preset = $scope.selectedPreset; |
| |
| _savePreset(preset); |
| |
| return agentMgr.schemas(preset); |
| }) |
| .then((schemaInfo) => { |
| $scope.importDomain.action = 'schemas'; |
| $scope.importDomain.info = INFO_SELECT_SCHEMAS; |
| $scope.importDomain.catalog = toJavaIdentifier(schemaInfo.catalog); |
| $scope.importDomain.schemas = _.map(schemaInfo.schemas, (schema) => ({name: schema})); |
| $scope.importDomain.schemasToUse = $scope.importDomain.schemas; |
| this.selectedSchemasIDs = $scope.importDomain.schemas.map((s) => s.name); |
| |
| if ($scope.importDomain.schemas.length === 0) |
| $scope.importDomainNext(); |
| }) |
| .catch(Messages.showError) |
| .then(() => Loading.finish('importDomainFromDb')); |
| }; |
| |
| |
| this._importCachesOrTemplates = []; |
| |
| $scope.tableActionView = (tbl) => { |
| const cacheName = get('label')(find({value: tbl.cacheOrTemplate})); |
| |
| if (tbl.action === IMPORT_DM_NEW_CACHE) |
| return 'Create ' + tbl.generatedCacheName + ' (' + cacheName + ')'; |
| |
| return 'Associate with ' + cacheName; |
| }; |
| |
| /** |
| * Load list of database tables. |
| */ |
| const _loadTables = () => { |
| agentMgr.awaitAgent() |
| .then(() => { |
| $scope.importDomain.loadingOptions = LOADING_TABLES; |
| Loading.start('importDomainFromDb'); |
| |
| $scope.importDomain.allTablesSelected = false; |
| this.selectedTables = []; |
| |
| const preset = $scope.importDomain.demo ? $scope.demoConnection : $scope.selectedPreset; |
| |
| preset.schemas = $scope.importDomain.schemasToUse.map((s) => s.name); |
| |
| return agentMgr.tables(preset); |
| }) |
| .then((tables) => { |
| this._importCachesOrTemplates = CACHE_TEMPLATES.concat($scope.caches); |
| |
| this._fillCommonCachesOrTemplates($scope.importCommon)($scope.importCommon.action); |
| |
| _.forEach(tables, (tbl, idx) => { |
| tbl.id = idx; |
| tbl.action = IMPORT_DM_NEW_CACHE; |
| // tbl.generatedCacheName = toJavaClassName(tbl.table) + 'Cache'; |
| tbl.generatedCacheName = uniqueName(toJavaClassName(tbl.table) + 'Cache', this.caches); |
| tbl.cacheOrTemplate = DFLT_PARTITIONED_CACHE.value; |
| tbl.label = tbl.schema + '.' + tbl.table; |
| tbl.edit = false; |
| }); |
| |
| $scope.importDomain.action = 'tables'; |
| $scope.importDomain.tables = tables; |
| const tablesToUse = tables.filter((t) => LegacyUtils.isDefined(_.find(t.columns, (col) => col.key))); |
| this.selectedTablesIDs = tablesToUse.map((t) => t.id); |
| this.$scope.importDomain.tablesToUse = tablesToUse; |
| |
| $scope.importDomain.info = INFO_SELECT_TABLES; |
| }) |
| .catch(Messages.showError) |
| .then(() => Loading.finish('importDomainFromDb')); |
| }; |
| |
| $scope.applyDefaults = () => { |
| _.forEach(this.visibleTables, (table) => { |
| table.edit = false; |
| table.action = $scope.importCommon.action; |
| table.cacheOrTemplate = $scope.importCommon.cacheOrTemplate; |
| }); |
| }; |
| |
| $scope._curDbTable = null; |
| |
| $scope.startEditDbTableCache = (tbl) => { |
| if ($scope._curDbTable) { |
| $scope._curDbTable.edit = false; |
| |
| if ($scope._curDbTable.actionWatch) { |
| $scope._curDbTable.actionWatch(); |
| $scope._curDbTable.actionWatch = null; |
| } |
| } |
| |
| $scope._curDbTable = tbl; |
| |
| const _fillFn = this._fillCommonCachesOrTemplates($scope._curDbTable); |
| |
| _fillFn($scope._curDbTable.action); |
| |
| $scope._curDbTable.actionWatch = $scope.$watch('_curDbTable.action', _fillFn, true); |
| |
| $scope._curDbTable.edit = true; |
| }; |
| |
| /** |
| * Show page with import domain models options. |
| */ |
| function _selectOptions() { |
| $scope.importDomain.action = 'options'; |
| $scope.importDomain.button = 'Save'; |
| $scope.importDomain.info = INFO_SELECT_OPTIONS; |
| |
| Focus.move('domainPackageName'); |
| } |
| |
| const _saveDomainModel = (optionsForm) => { |
| if (optionsForm.$invalid) |
| return this.FormUtils.triggerValidation(optionsForm, this.$scope); |
| |
| const generatePojo = $scope.ui.generatePojo; |
| const packageName = $scope.ui.packageName; |
| |
| const batch = []; |
| const checkedCaches = []; |
| |
| let containKey = true; |
| let containDup = false; |
| |
| function dbField(name, jdbcType, nullable, unsigned) { |
| const javaTypes = (unsigned && jdbcType.unsigned) ? jdbcType.unsigned : jdbcType.signed; |
| const javaFieldType = (!nullable && javaTypes.primitiveType && $scope.ui.usePrimitives) ? javaTypes.primitiveType : javaTypes.javaType; |
| |
| return { |
| databaseFieldName: name, |
| databaseFieldType: jdbcType.dbName, |
| javaType: javaTypes.javaType, |
| javaFieldName: toJavaFieldName(name), |
| javaFieldType |
| }; |
| } |
| |
| _.forEach($scope.importDomain.tablesToUse, (table, curIx, tablesToUse) => { |
| const qryFields = []; |
| const indexes = []; |
| const keyFields = []; |
| const valFields = []; |
| const aliases = []; |
| |
| const tableName = table.table; |
| let typeName = toJavaClassName(tableName); |
| |
| if (_.find($scope.importDomain.tablesToUse, |
| (tbl, ix) => ix !== curIx && tableName === tbl.table)) { |
| typeName = typeName + '_' + toJavaClassName(table.schema); |
| |
| containDup = true; |
| } |
| |
| let valType = tableName; |
| let typeAlias; |
| |
| if (generatePojo) { |
| if ($scope.ui.generateTypeAliases && tableName.toLowerCase() !== typeName.toLowerCase()) |
| typeAlias = tableName; |
| |
| valType = _toJavaPackage(packageName) + '.' + typeName; |
| } |
| |
| let _containKey = false; |
| |
| _.forEach(table.columns, function(col) { |
| const fld = dbField(col.name, SqlTypes.findJdbcType(col.type), col.nullable, col.unsigned); |
| |
| qryFields.push({name: fld.javaFieldName, className: fld.javaType}); |
| |
| const dbName = fld.databaseFieldName; |
| |
| if (generatePojo && $scope.ui.generateFieldAliases && |
| SqlTypes.validIdentifier(dbName) && !SqlTypes.isKeyword(dbName) && |
| !_.find(aliases, {field: fld.javaFieldName}) && |
| fld.javaFieldName.toUpperCase() !== dbName.toUpperCase()) |
| aliases.push({field: fld.javaFieldName, alias: dbName}); |
| |
| if (col.key) { |
| keyFields.push(fld); |
| |
| _containKey = true; |
| } |
| else |
| valFields.push(fld); |
| }); |
| |
| containKey &= _containKey; |
| if (table.indexes) { |
| _.forEach(table.indexes, (idx) => { |
| const idxFields = _.map(idx.fields, (idxFld) => ({ |
| name: toJavaFieldName(idxFld.name), |
| direction: idxFld.sortOrder |
| })); |
| |
| indexes.push({ |
| name: idx.name, |
| indexType: 'SORTED', |
| fields: idxFields |
| }); |
| }); |
| } |
| |
| const domainFound = _.find($scope.domains, (domain) => domain.valueType === valType); |
| |
| const batchAction = { |
| confirm: false, |
| skip: false, |
| table, |
| newDomainModel: { |
| _id: ObjectID.generate(), |
| caches: [], |
| generatePojo |
| } |
| }; |
| |
| if (LegacyUtils.isDefined(domainFound)) { |
| batchAction.newDomainModel._id = domainFound._id; |
| // Don't touch original caches value |
| delete batchAction.newDomainModel.caches; |
| batchAction.confirm = true; |
| } |
| |
| Object.assign(batchAction.newDomainModel, { |
| tableName: typeAlias, |
| keyType: valType + 'Key', |
| valueType: valType, |
| queryMetadata: 'Configuration', |
| databaseSchema: table.schema, |
| databaseTable: tableName, |
| fields: qryFields, |
| queryKeyFields: _.map(keyFields, (field) => field.javaFieldName), |
| indexes, |
| keyFields, |
| aliases, |
| valueFields: _.isEmpty(valFields) ? keyFields.slice() : valFields |
| }); |
| |
| // Use Java built-in type for key. |
| if ($scope.ui.builtinKeys && batchAction.newDomainModel.keyFields.length === 1) { |
| const newDomain = batchAction.newDomainModel; |
| const keyField = newDomain.keyFields[0]; |
| |
| newDomain.keyType = keyField.javaType; |
| newDomain.keyFieldName = keyField.javaFieldName; |
| |
| if (!$scope.ui.generateKeyFields) { |
| // Exclude key column from query fields. |
| newDomain.fields = _.filter(newDomain.fields, (field) => field.name !== keyField.javaFieldName); |
| |
| newDomain.queryKeyFields = []; |
| } |
| |
| // Exclude key column from indexes. |
| _.forEach(newDomain.indexes, (index) => { |
| index.fields = _.filter(index.fields, (field) => field.name !== keyField.javaFieldName); |
| }); |
| |
| newDomain.indexes = _.filter(newDomain.indexes, (index) => !_.isEmpty(index.fields)); |
| } |
| |
| // Prepare caches for generation. |
| if (table.action === IMPORT_DM_NEW_CACHE) { |
| const newCache = _.cloneDeep(this.loadedCaches[table.cacheOrTemplate]); |
| |
| batchAction.newCache = newCache; |
| |
| // const siblingCaches = batch.filter((a) => a.newCache).map((a) => a.newCache); |
| const siblingCaches = []; |
| newCache._id = ObjectID.generate(); |
| newCache.name = uniqueName(typeName + 'Cache', this.caches.concat(siblingCaches)); |
| newCache.domains = [batchAction.newDomainModel._id]; |
| batchAction.newDomainModel.caches = [newCache._id]; |
| |
| // POJO store factory is not defined in template. |
| if (!newCache.cacheStoreFactory || newCache.cacheStoreFactory.kind !== 'CacheJdbcPojoStoreFactory') { |
| const dialect = $scope.importDomain.demo ? 'H2' : $scope.selectedPreset.db; |
| |
| const catalog = $scope.importDomain.catalog; |
| |
| newCache.cacheStoreFactory = { |
| kind: 'CacheJdbcPojoStoreFactory', |
| CacheJdbcPojoStoreFactory: { |
| dataSourceBean: 'ds' + dialect + '_' + catalog, |
| dialect, |
| implementationVersion: $scope.selectedPreset.jdbcDriverImplementationVersion |
| }, |
| CacheJdbcBlobStoreFactory: { connectVia: 'DataSource' } |
| }; |
| } |
| |
| if (!newCache.readThrough && !newCache.writeThrough) { |
| newCache.readThrough = true; |
| newCache.writeThrough = true; |
| } |
| } |
| else { |
| const newDomain = batchAction.newDomainModel; |
| const cacheId = table.cacheOrTemplate; |
| |
| batchAction.newDomainModel.caches = [cacheId]; |
| |
| if (!_.includes(checkedCaches, cacheId)) { |
| const cache = _.find($scope.caches, {value: cacheId}).cache; |
| |
| // TODO: move elsewhere, make sure it still works |
| const change = LegacyUtils.autoCacheStoreConfiguration(cache, [newDomain]); |
| |
| if (change) |
| batchAction.cacheStoreChanges = [{cacheId, change}]; |
| |
| checkedCaches.push(cacheId); |
| } |
| } |
| |
| batch.push(batchAction); |
| }); |
| |
| /** |
| * Generate message to show on confirm dialog. |
| * |
| * @param meta Object to confirm. |
| * @returns {string} Generated message. |
| */ |
| function overwriteMessage(meta) { |
| return ` |
| Domain model with name "${meta.newDomainModel.databaseTable}" already exists. |
| Are you sure you want to overwrite it? |
| `; |
| } |
| |
| const itemsToConfirm = _.filter(batch, (item) => item.confirm); |
| |
| const checkOverwrite = () => { |
| if (itemsToConfirm.length > 0) { |
| return ConfirmBatch.confirm(overwriteMessage, itemsToConfirm) |
| .then(() => this.saveBatch(_.filter(batch, (item) => !item.skip))) |
| .catch(() => Messages.showError('Importing of domain models interrupted by user.')); |
| } |
| return this.saveBatch(batch); |
| }; |
| |
| const checkDuplicate = () => { |
| if (containDup) { |
| Confirm.confirm('Some tables have the same name.<br/>' + |
| 'Name of types for that tables will contain schema name too.') |
| .then(() => checkOverwrite()); |
| } |
| else |
| checkOverwrite(); |
| }; |
| |
| if (containKey) |
| checkDuplicate(); |
| else { |
| Confirm.confirm('Some tables have no primary key.<br/>' + |
| 'You will need to configure key type and key fields for such tables after import complete.') |
| .then(() => checkDuplicate()); |
| } |
| }; |
| |
| |
| $scope.importDomainNext = (form) => { |
| if (!$scope.importDomainNextAvailable()) |
| return; |
| |
| const act = $scope.importDomain.action; |
| |
| if (act === 'drivers' && $scope.importDomain.jdbcDriversNotFound) |
| importDomainModal.hide(); |
| else if (act === 'connect') |
| _loadSchemas(); |
| else if (act === 'schemas') |
| _loadTables(); |
| else if (act === 'tables') |
| _selectOptions(); |
| else if (act === 'options') |
| _saveDomainModel(form); |
| }; |
| |
| $scope.nextTooltipText = function() { |
| const importDomainNextAvailable = $scope.importDomainNextAvailable(); |
| |
| const act = $scope.importDomain.action; |
| |
| if (act === 'drivers' && $scope.importDomain.jdbcDriversNotFound) |
| return 'Resolve issue with JDBC drivers<br>Close this dialog and try again'; |
| |
| if (act === 'connect' && _.isNil($scope.selectedPreset.jdbcDriverClass)) |
| return 'Input valid JDBC driver class name'; |
| |
| if (act === 'connect' && _.isNil($scope.selectedPreset.jdbcUrl)) |
| return 'Input valid JDBC URL'; |
| |
| if (act === 'connect' || act === 'drivers') |
| return 'Click to load list of schemas from database'; |
| |
| if (act === 'schemas') |
| return importDomainNextAvailable ? 'Click to load list of tables from database' : 'Select schemas to continue'; |
| |
| if (act === 'tables') |
| return importDomainNextAvailable ? 'Click to show import options' : 'Select tables to continue'; |
| |
| if (act === 'options') |
| return 'Click to import domain model for selected tables'; |
| |
| return 'Click to continue'; |
| }; |
| |
| $scope.prevTooltipText = function() { |
| const act = $scope.importDomain.action; |
| |
| if (act === 'schemas') |
| return $scope.importDomain.demo ? 'Click to return on demo description step' : 'Click to return on connection configuration step'; |
| |
| if (act === 'tables') |
| return 'Click to return on schemas selection step'; |
| |
| if (act === 'options') |
| return 'Click to return on tables selection step'; |
| }; |
| |
| $scope.importDomainNextAvailable = function() { |
| switch ($scope.importDomain.action) { |
| case 'connect': |
| return !_.isNil($scope.selectedPreset.jdbcDriverClass) && !_.isNil($scope.selectedPreset.jdbcUrl); |
| |
| case 'schemas': |
| return _.isEmpty($scope.importDomain.schemas) || !!get('importDomain.schemasToUse.length')($scope); |
| |
| case 'tables': |
| return !!$scope.importDomain.tablesToUse.length; |
| |
| default: |
| return true; |
| } |
| }; |
| |
| $scope.importDomainPrev = function() { |
| $scope.importDomain.button = 'Next'; |
| |
| if ($scope.importDomain.action === 'options') { |
| $scope.importDomain.action = 'tables'; |
| $scope.importDomain.info = INFO_SELECT_TABLES; |
| } |
| else if ($scope.importDomain.action === 'tables' && $scope.importDomain.schemas.length > 0) { |
| $scope.importDomain.action = 'schemas'; |
| $scope.importDomain.info = INFO_SELECT_SCHEMAS; |
| } |
| else { |
| $scope.importDomain.action = 'connect'; |
| $scope.importDomain.info = INFO_CONNECT_TO_DB; |
| } |
| }; |
| |
| const demo = $root.IgniteDemoMode; |
| |
| $scope.importDomain = { |
| demo, |
| action: demo ? 'connect' : 'drivers', |
| jdbcDriversNotFound: demo, |
| schemas: [], |
| allSchemasSelected: false, |
| tables: [], |
| allTablesSelected: false, |
| button: 'Next', |
| info: '' |
| }; |
| |
| $scope.importDomain.loadingOptions = LOADING_JDBC_DRIVERS; |
| |
| const fetchDomainData = () => { |
| return agentMgr.awaitAgent() |
| .then(() => { |
| ActivitiesData.post({ |
| group: 'configuration', |
| action: 'configuration/import/model' |
| }); |
| |
| return true; |
| }) |
| .then(() => { |
| if (demo) { |
| $scope.ui.packageNameUserInput = $scope.ui.packageName; |
| $scope.ui.packageName = 'model'; |
| |
| return; |
| } |
| |
| // Get available JDBC drivers via agent. |
| Loading.start('importDomainFromDb'); |
| |
| $scope.jdbcDriverJars = []; |
| $scope.ui.selectedJdbcDriverJar = {}; |
| |
| return agentMgr.drivers() |
| .then((drivers) => { |
| $scope.ui.packageName = $scope.ui.packageNameUserInput; |
| |
| if (drivers && drivers.length > 0) { |
| drivers = _.sortBy(drivers, 'jdbcDriverJar'); |
| |
| _.forEach(drivers, (drv) => { |
| $scope.jdbcDriverJars.push({ |
| label: drv.jdbcDriverJar, |
| value: { |
| jdbcDriverJar: drv.jdbcDriverJar, |
| jdbcDriverClass: drv.jdbcDriverCls, |
| jdbcDriverImplementationVersion: drv.jdbcDriverImplVersion |
| } |
| }); |
| }); |
| |
| $scope.ui.selectedJdbcDriverJar = $scope.jdbcDriverJars[0].value; |
| |
| $scope.importDomain.action = 'connect'; |
| $scope.importDomain.tables = []; |
| this.selectedTables = []; |
| } |
| else { |
| $scope.importDomain.jdbcDriversNotFound = true; |
| $scope.importDomain.button = 'Cancel'; |
| } |
| }) |
| .then(() => { |
| $scope.importDomain.info = INFO_CONNECT_TO_DB; |
| |
| Loading.finish('importDomainFromDb'); |
| }); |
| }); |
| }; |
| |
| this.agentIsAvailable$ = this.agentMgr.connectionSbj.pipe( |
| pluck('state'), |
| distinctUntilChanged(), |
| map((state) => state !== 'AGENT_DISCONNECTED') |
| ); |
| |
| this.domainData$ = this.agentIsAvailable$.pipe( |
| switchMap((agentIsAvailable) => { |
| if (!agentIsAvailable) |
| return of(EMPTY); |
| |
| return from(fetchDomainData()); |
| }) |
| ); |
| |
| this.subscribers$ = merge( |
| this.subscription, |
| this.domainData$ |
| ).subscribe(); |
| |
| $scope.$watch('ui.selectedJdbcDriverJar', function(val) { |
| if (val && !$scope.importDomain.demo) { |
| const foundPreset = _findPreset(val); |
| |
| const selectedPreset = $scope.selectedPreset; |
| |
| selectedPreset.db = foundPreset.db; |
| selectedPreset.jdbcDriverJar = foundPreset.jdbcDriverJar; |
| selectedPreset.jdbcDriverClass = foundPreset.jdbcDriverClass; |
| selectedPreset.jdbcDriverImplementationVersion = foundPreset.jdbcDriverImplementationVersion; |
| selectedPreset.jdbcUrl = foundPreset.jdbcUrl; |
| selectedPreset.user = foundPreset.user; |
| } |
| }, true); |
| } |
| |
| _fillCommonCachesOrTemplates(item) { |
| return (action) => { |
| if (item.cachesOrTemplates) |
| item.cachesOrTemplates.length = 0; |
| else |
| item.cachesOrTemplates = []; |
| |
| if (action === IMPORT_DM_NEW_CACHE) |
| item.cachesOrTemplates.push(...CACHE_TEMPLATES); |
| |
| if (!_.isEmpty(this.$scope.caches)) { |
| item.cachesOrTemplates.push(...this.$scope.caches); |
| this.onCacheSelect(item.cachesOrTemplates[0].value); |
| } |
| |
| if ( |
| !_.find(item.cachesOrTemplates, {value: item.cacheOrTemplate}) && |
| item.cachesOrTemplates.length |
| ) |
| item.cacheOrTemplate = item.cachesOrTemplates[0].value; |
| }; |
| } |
| |
| schemasColumnDefs = [ |
| { |
| name: 'name', |
| displayName: 'Name', |
| field: 'name', |
| enableHiding: false, |
| sort: {direction: 'asc', priority: 0}, |
| filter: { |
| placeholder: 'Filter by Name…' |
| }, |
| visible: true, |
| sortingAlgorithm: naturalCompare, |
| minWidth: 165 |
| } |
| ]; |
| |
| tablesColumnDefs = [ |
| { |
| name: 'schema', |
| displayName: 'Schema', |
| field: 'schema', |
| enableHiding: false, |
| enableFiltering: false, |
| sort: {direction: 'asc', priority: 0}, |
| visible: true, |
| sortingAlgorithm: naturalCompare, |
| minWidth: 100 |
| }, |
| { |
| name: 'table', |
| displayName: 'Table', |
| field: 'table', |
| enableHiding: false, |
| enableFiltering: true, |
| filter: { |
| placeholder: 'Filter by Table…' |
| }, |
| visible: true, |
| sortingAlgorithm: naturalCompare, |
| minWidth: 200 |
| }, |
| { |
| name: 'action', |
| displayName: 'Action', |
| field: 'action', |
| enableHiding: false, |
| enableFiltering: false, |
| cellTemplate: ` |
| <tables-action-cell |
| table='row.entity' |
| on-edit-start='grid.appScope.$ctrl.$scope.startEditDbTableCache($event)' |
| on-cache-select='grid.appScope.$ctrl.onCacheSelect($event)' |
| caches='grid.appScope.$ctrl._importCachesOrTemplates' |
| import-actions='grid.appScope.$ctrl.$scope.importActions' |
| ></tables-action-cell> |
| `, |
| visible: true, |
| minWidth: 450 |
| } |
| ]; |
| } |
| |
| export const component = { |
| name: 'modalImportModels', |
| controller: ModalImportModels, |
| templateUrl, |
| bindings: { |
| onHide: '&', |
| clusterID: '<clusterId' |
| } |
| }; |