/* * DBeaver - Universal Database Manager * Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org) * * Licensed 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. */ package org.jkiss.dbeaver.registry; import org.eclipse.core.resources.*; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.equinox.security.storage.ISecurePreferences; import org.eclipse.equinox.security.storage.SecurePreferencesFactory; import org.eclipse.equinox.security.storage.StorageException; import org.jkiss.code.NotNull; import org.jkiss.code.Nullable; import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.Log; import org.jkiss.dbeaver.core.DBeaverCore; import org.jkiss.dbeaver.core.DBeaverNature; import org.jkiss.dbeaver.model.*; import org.jkiss.dbeaver.model.app.DBASecureStorage; import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry; import org.jkiss.dbeaver.model.app.DBPPlatform; import org.jkiss.dbeaver.model.connection.DBPConnectionBootstrap; import org.jkiss.dbeaver.model.connection.DBPConnectionConfiguration; import org.jkiss.dbeaver.model.connection.DBPConnectionEventType; import org.jkiss.dbeaver.model.connection.DBPConnectionType; import org.jkiss.dbeaver.model.impl.preferences.SimplePreferenceStore; import org.jkiss.dbeaver.model.net.DBWHandlerConfiguration; import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor; import org.jkiss.dbeaver.model.runtime.DBRRunnableWithProgress; import org.jkiss.dbeaver.model.runtime.DBRShellCommand; import org.jkiss.dbeaver.model.struct.DBSObject; import org.jkiss.dbeaver.model.struct.DBSObjectFilter; import org.jkiss.dbeaver.model.struct.rdb.DBSCatalog; import org.jkiss.dbeaver.model.struct.rdb.DBSSchema; import org.jkiss.dbeaver.registry.driver.DriverDescriptor; import org.jkiss.dbeaver.registry.encode.EncryptionException; import org.jkiss.dbeaver.registry.encode.PasswordEncrypter; import org.jkiss.dbeaver.registry.encode.SimpleStringEncrypter; import org.jkiss.dbeaver.registry.network.NetworkHandlerDescriptor; import org.jkiss.dbeaver.registry.network.NetworkHandlerRegistry; import org.jkiss.dbeaver.utils.GeneralUtils; import org.jkiss.dbeaver.utils.RuntimeUtils; import org.jkiss.utils.ArrayUtils; import org.jkiss.utils.CommonUtils; import org.jkiss.utils.xml.SAXListener; import org.jkiss.utils.xml.SAXReader; import org.jkiss.utils.xml.XMLBuilder; import org.jkiss.utils.xml.XMLException; import org.xml.sax.Attributes; import java.io.*; import java.lang.reflect.InvocationTargetException; import java.util.*; public class DataSourceRegistry implements DBPDataSourceRegistry { @Deprecated public static final String DEFAULT_AUTO_COMMIT = "default.autocommit"; //$NON-NLS-1$ @Deprecated public static final String DEFAULT_ISOLATION = "default.isolation"; //$NON-NLS-1$ @Deprecated public static final String DEFAULT_ACTIVE_OBJECT = "default.activeObject"; //$NON-NLS-1$ private static final long DISCONNECT_ALL_TIMEOUT = 5000; private static final Log log = Log.getLog(DataSourceRegistry.class); public static final String OLD_CONFIG_FILE_NAME = "data-sources.xml"; //$NON-NLS-1$ private static PasswordEncrypter ENCRYPTOR = new SimpleStringEncrypter(); private final DBPPlatform platform; private final IProject project; private final Map<IFile, DataSourceOrigin> origins = new LinkedHashMap<>(); private final List<DataSourceDescriptor> dataSources = new ArrayList<>(); private final List<DBPEventListener> dataSourceListeners = new ArrayList<>(); private final List<DataSourceFolder> dataSourceFolders = new ArrayList<>(); private volatile boolean saveInProgress = false; public DataSourceRegistry(DBPPlatform platform, IProject project) { this.platform = platform; this.project = project; loadDataSources(false); DataSourceProviderRegistry.getInstance().fireRegistryChange(this, true); } public void dispose() { DataSourceProviderRegistry.getInstance().fireRegistryChange(this, false); synchronized (dataSourceListeners) { if (!this.dataSourceListeners.isEmpty()) { log.warn("Some data source listeners are still registered: " + dataSourceListeners); } this.dataSourceListeners.clear(); } // Disconnect in 5 seconds or die closeConnections(DISCONNECT_ALL_TIMEOUT); // Do not save config on shutdown. // Some data source might be broken due to misconfiguration // and we don't want to loose their config just after restart // if (getProject().isOpen()) { // flushConfig(); // } // Dispose and clear all descriptors synchronized (dataSources) { for (DataSourceDescriptor dataSourceDescriptor : this.dataSources) { dataSourceDescriptor.dispose(); } this.dataSources.clear(); } } private void closeConnections(long waitTime) { boolean hasConnections = false; synchronized (dataSources) { for (DataSourceDescriptor dataSource : dataSources) { if (dataSource.isConnected()) { hasConnections = true; break; } } } if (!hasConnections) { return; } final DisconnectTask disconnectTask = new DisconnectTask(); if (!RuntimeUtils.runTask(disconnectTask, "Disconnect from data sources", waitTime)) { log.warn("Some data source connections wasn't closed on shutdown in " + waitTime + "ms. Probably network timeout occurred."); } } DataSourceOrigin getDefaultOrigin() { synchronized (origins) { for (DataSourceOrigin origin : origins.values()) { if (origin.isDefault()) { return origin; } } IFile defFile = project.getFile(CONFIG_FILE_NAME); DataSourceOrigin origin = new DataSourceOrigin(defFile, true); origins.put(defFile, origin); return origin; } } @NotNull public DBPPlatform getPlatform() { return platform; } //////////////////////////////////////////////////// // Data sources @Nullable @Override public DataSourceDescriptor getDataSource(String id) { synchronized (dataSources) { for (DataSourceDescriptor dsd : dataSources) { if (dsd.getId().equals(id)) { return dsd; } } } return null; } @Nullable @Override public DataSourceDescriptor getDataSource(DBPDataSource dataSource) { synchronized (dataSources) { for (DataSourceDescriptor dsd : dataSources) { if (dsd.getDataSource() == dataSource) { return dsd; } } } return null; } @Nullable @Override public DataSourceDescriptor findDataSourceByName(String name) { synchronized (dataSources) { for (DataSourceDescriptor dsd : dataSources) { if (dsd.getName().equals(name)) { return dsd; } } } return null; } @Override public List<DataSourceDescriptor> getDataSources() { List<DataSourceDescriptor> dsCopy; synchronized (dataSources) { dsCopy = CommonUtils.copyList(dataSources); } Collections.sort(dsCopy, new Comparator<DataSourceDescriptor>() { @Override public int compare(DataSourceDescriptor o1, DataSourceDescriptor o2) { return o1.getName().compareToIgnoreCase(o2.getName()); } }); return dsCopy; } @Override public List<? extends DBPDataSourceFolder> getAllFolders() { return dataSourceFolders; } @Override public List<DataSourceFolder> getRootFolders() { List<DataSourceFolder> rootFolders = new ArrayList<>(); for (DataSourceFolder folder : dataSourceFolders) { if (folder.getParent() == null) { rootFolders.add(folder); } } return rootFolders; } @Override public DataSourceFolder addFolder(DBPDataSourceFolder parent, String name) { DataSourceFolder folder = new DataSourceFolder(this, (DataSourceFolder) parent, name, null); dataSourceFolders.add(folder); return folder; } @Override public void removeFolder(DBPDataSourceFolder folder, boolean dropContents) { final DataSourceFolder folderImpl = (DataSourceFolder) folder; for (DataSourceFolder child : folderImpl.getChildren()) { removeFolder(child, dropContents); } final DBPDataSourceFolder parent = folder.getParent(); if (parent != null) { folderImpl.setParent(null); } for (DataSourceDescriptor ds : dataSources) { if (ds.getFolder() == folder) { if (dropContents) { removeDataSource(ds); } else { ds.setFolder(parent); } } } dataSourceFolders.remove(folderImpl); } private DataSourceFolder findRootFolder(String name) { for (DataSourceFolder root : getRootFolders()) { if (root.getName().equals(name)) { return root; } } return null; } public DBPDataSourceFolder getFolder(String path) { return findFolderByPath(path, true); } private DataSourceFolder findFolderByPath(String path, boolean create) { DataSourceFolder parent = null; for (String name : path.split("/")) { DataSourceFolder folder = parent == null ? findRootFolder(name) : parent.getChild(name); if (folder == null) { if (!create) { log.warn("Folder '" + path + "' not found"); break; } else { folder = addFolder(parent, name); } } parent = folder; } return parent; } public void addDataSource(DBPDataSourceContainer dataSource) { final DataSourceDescriptor descriptor = (DataSourceDescriptor) dataSource; synchronized (dataSources) { this.dataSources.add(descriptor); } if (!dataSource.isTemporary()) { this.saveDataSources(); } notifyDataSourceListeners(new DBPEvent(DBPEvent.Action.OBJECT_ADD, descriptor, true)); } public void removeDataSource(DBPDataSourceContainer dataSource) { final DataSourceDescriptor descriptor = (DataSourceDescriptor) dataSource; synchronized (dataSources) { this.dataSources.remove(descriptor); } if (!dataSource.isTemporary()) { this.saveDataSources(); } try { this.fireDataSourceEvent(DBPEvent.Action.OBJECT_REMOVE, dataSource); } finally { descriptor.dispose(); } } public void updateDataSource(DBPDataSourceContainer dataSource) { if (!dataSource.isTemporary()) { this.saveDataSources(); } this.fireDataSourceEvent(DBPEvent.Action.OBJECT_UPDATE, dataSource); } @Override public void flushConfig() { this.saveDataSources(); } @Override public void refreshConfig() { if (!saveInProgress) { this.loadDataSources(true); } } @Override public void addDataSourceListener(DBPEventListener listener) { synchronized (dataSourceListeners) { dataSourceListeners.add(listener); } } @Override public boolean removeDataSourceListener(DBPEventListener listener) { synchronized (dataSourceListeners) { return dataSourceListeners.remove(listener); } } private void fireDataSourceEvent( DBPEvent.Action action, DBSObject object) { notifyDataSourceListeners(new DBPEvent(action, object)); } public void notifyDataSourceListeners(final DBPEvent event) { if (dataSourceListeners.isEmpty()) { return; } final List<DBPEventListener> listeners; synchronized (dataSourceListeners) { listeners = new ArrayList<>(dataSourceListeners); } for (DBPEventListener listener : listeners) { listener.handleDataSourceEvent(event); } } @Override @NotNull public ISecurePreferences getSecurePreferences() { return SecurePreferencesFactory.getDefault().node("dbeaver").node("datasources"); } public static List<DataSourceRegistry> getAllRegistries() { List<DataSourceRegistry> result = new ArrayList<>(); for (IProject project : DBeaverCore.getInstance().getLiveProjects()) { if (project.isOpen()) { DataSourceRegistry registry = DBeaverCore.getInstance().getProjectRegistry().getDataSourceRegistry(project); if (registry != null) { result.add(registry); } } } return result; } public static List<DataSourceDescriptor> getAllDataSources() { List<DataSourceDescriptor> result = new ArrayList<>(); for (IProject project : DBeaverCore.getInstance().getLiveProjects()) { if (project.isOpen()) { DataSourceRegistry registry = DBeaverCore.getInstance().getProjectRegistry().getDataSourceRegistry(project); if (registry != null) { result.addAll(registry.getDataSources()); } } } return result; } /** * Find data source in all available registries */ public static DataSourceDescriptor findDataSource(String dataSourceId) { ProjectRegistry projectRegistry = DBeaverCore.getInstance().getProjectRegistry(); for (IProject project : DBeaverCore.getInstance().getLiveProjects()) { DataSourceRegistry dataSourceRegistry = projectRegistry.getDataSourceRegistry(project); if (dataSourceRegistry != null) { DataSourceDescriptor dataSourceContainer = dataSourceRegistry.getDataSource(dataSourceId); if (dataSourceContainer != null) { return dataSourceContainer; } } } return null; } private void loadDataSources(boolean refresh) { if (!project.isOpen()) { return; } ParseResults parseResults = new ParseResults(); try { for (IResource res : project.members(IContainer.INCLUDE_HIDDEN)) { if (res instanceof IFile) { IFile file = (IFile) res; if (res.getName().startsWith(CONFIG_FILE_PREFIX) && res.getName().endsWith(CONFIG_FILE_EXT)) { if (file.exists()) { if (file.exists()) { loadDataSources(file, refresh, parseResults); } } } } } } catch (CoreException e) { log.error("Error reading datasources configuration", e); } // Reflect changes if (refresh) { for (DataSourceDescriptor ds : parseResults.updatedDataSources) { fireDataSourceEvent(DBPEvent.Action.OBJECT_UPDATE, ds); } for (DataSourceDescriptor ds : parseResults.addedDataSources) { fireDataSourceEvent(DBPEvent.Action.OBJECT_ADD, ds); } List<DataSourceDescriptor> removedDataSource = new ArrayList<>(); for (DataSourceDescriptor ds : dataSources) { if (!parseResults.addedDataSources.contains(ds) && !parseResults.updatedDataSources.contains(ds)) { removedDataSource.add(ds); } } for (DataSourceDescriptor ds : removedDataSource) { this.dataSources.remove(ds); this.fireDataSourceEvent(DBPEvent.Action.OBJECT_REMOVE, ds); ds.dispose(); } } } private void loadDataSources(IFile fromFile, boolean refresh, ParseResults parseResults) { boolean extraConfig = !fromFile.getName().equalsIgnoreCase(CONFIG_FILE_NAME); DataSourceOrigin origin; synchronized (origins) { origin = origins.get(fromFile); if (origin == null) { origin = new DataSourceOrigin(fromFile, !extraConfig); origins.put(fromFile, origin); } } if (!fromFile.exists()) { return; } try (InputStream is = fromFile.getContents()) { loadDataSources(is, origin, refresh, parseResults); } catch (DBException ex) { log.warn("Error loading datasource config from " + fromFile.getFullPath(), ex); } catch (IOException ex) { log.warn("IO error", ex); } catch (CoreException e) { log.warn("Resource error", e); } } private void loadDataSources(InputStream is, DataSourceOrigin origin, boolean refresh, ParseResults parseResults) throws DBException, IOException { SAXReader parser = new SAXReader(is); try { final DataSourcesParser dsp = new DataSourcesParser(origin, refresh, parseResults); parser.parse(dsp); } catch (XMLException ex) { throw new DBException("Datasource config parse error", ex); } updateProjectNature(); } private void saveDataSources() { updateProjectNature(); final IProgressMonitor progressMonitor = new NullProgressMonitor(); saveInProgress = true; try { for (DataSourceOrigin origin : origins.values()) { List<DataSourceDescriptor> localDataSources = getDataSources(origin); IFile configFile = origin.getSourceFile(); try { if (localDataSources.isEmpty()) { configFile.delete(true, false, progressMonitor); } else { // Save in temp memory to be safe (any error during direct write will corrupt configuration) ByteArrayOutputStream tempStream = new ByteArrayOutputStream(10000); try { XMLBuilder xml = new XMLBuilder(tempStream, GeneralUtils.UTF8_ENCODING); xml.setButify(true); xml.startElement("data-sources"); if (origin.isDefault()) { // Folders (only for default origin) for (DataSourceFolder folder : dataSourceFolders) { saveFolder(xml, folder); } } // Datasources for (DataSourceDescriptor dataSource : localDataSources) { // Skip temporary if (!dataSource.isTemporary()) { saveDataSource(xml, dataSource); } } xml.endElement(); xml.flush(); } catch (IOException ex) { log.warn("IO error while saving datasources", ex); } InputStream ifs = new ByteArrayInputStream(tempStream.toByteArray()); if (!configFile.exists()) { configFile.create(ifs, true, progressMonitor); configFile.setHidden(true); } else { configFile.setContents(ifs, true, false, progressMonitor); } } try { getSecurePreferences().flush(); } catch (IOException e) { log.error("Error saving secured preferences", e); } } catch (CoreException ex) { log.error("Error saving datasources configuration", ex); } } } finally { saveInProgress = false; } } private List<DataSourceDescriptor> getDataSources(DataSourceOrigin origin) { List<DataSourceDescriptor> result = new ArrayList<>(); synchronized (dataSources) { for (DataSourceDescriptor ds : dataSources) { if (ds.getOrigin() == origin) { result.add(ds); } } } return result; } private void updateProjectNature() { try { final IProjectDescription description = project.getDescription(); if (description != null) { String[] natureIds = description.getNatureIds(); if (dataSources.isEmpty()) { // Remove nature if (ArrayUtils.contains(natureIds, DBeaverNature.NATURE_ID)) { description.setNatureIds(ArrayUtils.remove(String.class, natureIds, DBeaverNature.NATURE_ID)); project.setDescription(description, new NullProgressMonitor()); } } else { // Add nature if (!ArrayUtils.contains(natureIds, DBeaverNature.NATURE_ID)) { description.setNatureIds(ArrayUtils.add(String.class, natureIds, DBeaverNature.NATURE_ID)); try { project.setDescription(description, new NullProgressMonitor()); } catch (CoreException e) { log.debug("Can't set project nature", e); } } } } } catch (Exception e) { log.debug(e); } } private void saveFolder(XMLBuilder xml, DataSourceFolder folder) throws IOException { xml.startElement(RegistryConstants.TAG_FOLDER); if (folder.getParent() != null) { xml.addAttribute(RegistryConstants.ATTR_PARENT, folder.getParent().getFolderPath()); } xml.addAttribute(RegistryConstants.ATTR_NAME, folder.getName()); if (!CommonUtils.isEmpty(folder.getDescription())) { xml.addAttribute(RegistryConstants.ATTR_DESCRIPTION, folder.getDescription()); } xml.endElement(); } private void saveDataSource(XMLBuilder xml, DataSourceDescriptor dataSource) throws IOException { clearSecuredPasswords(dataSource); xml.startElement(RegistryConstants.TAG_DATA_SOURCE); xml.addAttribute(RegistryConstants.ATTR_ID, dataSource.getId()); xml.addAttribute(RegistryConstants.ATTR_PROVIDER, dataSource.getDriver().getProviderDescriptor().getId()); xml.addAttribute(RegistryConstants.ATTR_DRIVER, dataSource.getDriver().getId()); xml.addAttribute(RegistryConstants.ATTR_NAME, dataSource.getName()); xml.addAttribute(RegistryConstants.ATTR_SAVE_PASSWORD, dataSource.isSavePassword()); if (dataSource.isShowSystemObjects()) { xml.addAttribute(RegistryConstants.ATTR_SHOW_SYSTEM_OBJECTS, dataSource.isShowSystemObjects()); } if (dataSource.isShowUtilityObjects()) { xml.addAttribute(RegistryConstants.ATTR_SHOW_UTIL_OBJECTS, dataSource.isShowUtilityObjects()); } xml.addAttribute(RegistryConstants.ATTR_READ_ONLY, dataSource.isConnectionReadOnly()); if (dataSource.getFolder() != null) { xml.addAttribute(RegistryConstants.ATTR_FOLDER, dataSource.getFolder().getFolderPath()); } final String lockPasswordHash = dataSource.getLockPasswordHash(); if (!CommonUtils.isEmpty(lockPasswordHash)) { xml.addAttribute(RegistryConstants.ATTR_LOCK_PASSWORD, lockPasswordHash); } { // Connection info DBPConnectionConfiguration connectionInfo = dataSource.getConnectionConfiguration(); xml.startElement(RegistryConstants.TAG_CONNECTION); if (!CommonUtils.isEmpty(connectionInfo.getHostName())) { xml.addAttribute(RegistryConstants.ATTR_HOST, connectionInfo.getHostName()); } if (!CommonUtils.isEmpty(connectionInfo.getHostPort())) { xml.addAttribute(RegistryConstants.ATTR_PORT, connectionInfo.getHostPort()); } xml.addAttribute(RegistryConstants.ATTR_SERVER, CommonUtils.notEmpty(connectionInfo.getServerName())); xml.addAttribute(RegistryConstants.ATTR_DATABASE, CommonUtils.notEmpty(connectionInfo.getDatabaseName())); xml.addAttribute(RegistryConstants.ATTR_URL, CommonUtils.notEmpty(connectionInfo.getUrl())); saveSecuredCredentials(xml, dataSource, null, connectionInfo.getUserName(), dataSource.isSavePassword() ? connectionInfo.getUserPassword() : null); if (!CommonUtils.isEmpty(connectionInfo.getClientHomeId())) { xml.addAttribute(RegistryConstants.ATTR_HOME, connectionInfo.getClientHomeId()); } if (connectionInfo.getConnectionType() != null) { xml.addAttribute(RegistryConstants.ATTR_TYPE, connectionInfo.getConnectionType().getId()); } if (connectionInfo.getConnectionColor() != null) { xml.addAttribute(RegistryConstants.ATTR_COLOR, connectionInfo.getConnectionColor()); } // Save other if (connectionInfo.getKeepAliveInterval() > 0) { xml.addAttribute(RegistryConstants.ATTR_KEEP_ALIVE, connectionInfo.getKeepAliveInterval()); } for (Map.Entry<String, String> entry : connectionInfo.getProperties().entrySet()) { xml.startElement(RegistryConstants.TAG_PROPERTY); xml.addAttribute(RegistryConstants.ATTR_NAME, CommonUtils.toString(entry.getKey())); xml.addAttribute(RegistryConstants.ATTR_VALUE, CommonUtils.toString(entry.getValue())); xml.endElement(); } for (Map.Entry<String, String> entry : connectionInfo.getProviderProperties().entrySet()) { xml.startElement(RegistryConstants.TAG_PROVIDER_PROPERTY); xml.addAttribute(RegistryConstants.ATTR_NAME, CommonUtils.toString(entry.getKey())); xml.addAttribute(RegistryConstants.ATTR_VALUE, CommonUtils.toString(entry.getValue())); xml.endElement(); } // Save events for (DBPConnectionEventType eventType : connectionInfo.getDeclaredEvents()) { DBRShellCommand command = connectionInfo.getEvent(eventType); xml.startElement(RegistryConstants.TAG_EVENT); xml.addAttribute(RegistryConstants.ATTR_TYPE, eventType.name()); xml.addAttribute(RegistryConstants.ATTR_ENABLED, command.isEnabled()); xml.addAttribute(RegistryConstants.ATTR_SHOW_PANEL, command.isShowProcessPanel()); xml.addAttribute(RegistryConstants.ATTR_WAIT_PROCESS, command.isWaitProcessFinish()); if (command.isWaitProcessFinish()) { xml.addAttribute(RegistryConstants.ATTR_WAIT_PROCESS_TIMEOUT, command.getWaitProcessTimeoutMs()); } xml.addAttribute(RegistryConstants.ATTR_TERMINATE_AT_DISCONNECT, command.isTerminateAtDisconnect()); xml.addText(command.getCommand()); xml.endElement(); } // Save network handlers' configurations for (DBWHandlerConfiguration configuration : connectionInfo.getDeclaredHandlers()) { xml.startElement(RegistryConstants.TAG_NETWORK_HANDLER); xml.addAttribute(RegistryConstants.ATTR_TYPE, configuration.getType().name()); xml.addAttribute(RegistryConstants.ATTR_ID, CommonUtils.notEmpty(configuration.getId())); xml.addAttribute(RegistryConstants.ATTR_ENABLED, configuration.isEnabled()); xml.addAttribute(RegistryConstants.ATTR_SAVE_PASSWORD, configuration.isSavePassword()); if (!CommonUtils.isEmpty(configuration.getUserName())) { saveSecuredCredentials( xml, dataSource, "network/" + configuration.getId(), configuration.getUserName(), configuration.isSavePassword() ? configuration.getPassword() : null); } for (Map.Entry<String, String> entry : configuration.getProperties().entrySet()) { if (CommonUtils.isEmpty(entry.getValue())) { continue; } xml.startElement(RegistryConstants.TAG_PROPERTY); xml.addAttribute(RegistryConstants.ATTR_NAME, entry.getKey()); xml.addAttribute(RegistryConstants.ATTR_VALUE, CommonUtils.notEmpty(entry.getValue())); xml.endElement(); } xml.endElement(); } // Save bootstrap info { DBPConnectionBootstrap bootstrap = connectionInfo.getBootstrap(); if (bootstrap.hasData()) { xml.startElement(RegistryConstants.TAG_BOOTSTRAP); if (bootstrap.getDefaultAutoCommit() != null) { xml.addAttribute(RegistryConstants.ATTR_AUTOCOMMIT, bootstrap.getDefaultAutoCommit()); } if (bootstrap.getDefaultTransactionIsolation() != null) { xml.addAttribute(RegistryConstants.ATTR_TXN_ISOLATION, bootstrap.getDefaultTransactionIsolation()); } if (!CommonUtils.isEmpty(bootstrap.getDefaultObjectName())) { xml.addAttribute(RegistryConstants.ATTR_DEFAULT_OBJECT, bootstrap.getDefaultObjectName()); } if (bootstrap.isIgnoreErrors()) { xml.addAttribute(RegistryConstants.ATTR_IGNORE_ERRORS, true); } for (String query : bootstrap.getInitQueries()) { xml.startElement(RegistryConstants.TAG_QUERY); xml.addText(query); xml.endElement(); } xml.endElement(); } } xml.endElement(); } { // Filters Collection<FilterMapping> filterMappings = dataSource.getObjectFilters(); if (!CommonUtils.isEmpty(filterMappings)) { xml.startElement(RegistryConstants.TAG_FILTERS); for (FilterMapping filter : filterMappings) { if (filter.defaultFilter != null && !filter.defaultFilter.isEmpty()) { saveObjectFiler(xml, filter.typeName, null, filter.defaultFilter); } for (Map.Entry<String,DBSObjectFilter> cf : filter.customFilters.entrySet()) { if (!cf.getValue().isEmpty()) { saveObjectFiler(xml, filter.typeName, cf.getKey(), cf.getValue()); } } } xml.endElement(); } } // Virtual model if (dataSource.getVirtualModel().hasValuableData()) { xml.startElement(RegistryConstants.TAG_VIRTUAL_META_DATA); dataSource.getVirtualModel().serialize(xml); xml.endElement(); } // Preferences { // Save only properties who are differs from default values SimplePreferenceStore prefStore = dataSource.getPreferenceStore(); for (String propName : prefStore.preferenceNames()) { String propValue = prefStore.getString(propName); String defValue = prefStore.getDefaultString(propName); if (propValue == null || CommonUtils.equalObjects(propValue, defValue)) { continue; } xml.startElement(RegistryConstants.TAG_CUSTOM_PROPERTY); xml.addAttribute(RegistryConstants.ATTR_NAME, propName); xml.addAttribute(RegistryConstants.ATTR_VALUE, propValue); xml.endElement(); } } if (!CommonUtils.isEmpty(dataSource.getDescription())) { xml.startElement(RegistryConstants.TAG_DESCRIPTION); xml.addText(dataSource.getDescription()); xml.endElement(); } xml.endElement(); } private void saveSecuredCredentials(XMLBuilder xml, DataSourceDescriptor dataSource, String subNode, String userName, String password) throws IOException { boolean saved = false; final DBASecureStorage secureStorage = getPlatform().getSecureStorage(); { try { ISecurePreferences prefNode = dataSource.getSecurePreferences(); if (!secureStorage.useSecurePreferences()) { prefNode.removeNode(); } else { if (subNode != null) { for (String nodeName : subNode.split("/")) { prefNode = prefNode.node(nodeName); } } prefNode.put("name", dataSource.getName(), false); if (!CommonUtils.isEmpty(userName)) { prefNode.put(RegistryConstants.ATTR_USER, userName, true); saved = true; } else { prefNode.remove(RegistryConstants.ATTR_USER); } if (!CommonUtils.isEmpty(password)) { prefNode.put(RegistryConstants.ATTR_PASSWORD, password, true); saved = true; } else { prefNode.remove(RegistryConstants.ATTR_PASSWORD); } } } catch (StorageException e) { log.error("Can't save password in secure storage", e); } } if (!saved) { try { if (!CommonUtils.isEmpty(userName)) { xml.addAttribute(RegistryConstants.ATTR_USER, CommonUtils.notEmpty(userName)); } if (!CommonUtils.isEmpty(password)) { xml.addAttribute(RegistryConstants.ATTR_PASSWORD, ENCRYPTOR.encrypt(password)); } } catch (EncryptionException e) { log.error("Error encrypting password", e); } } } private void clearSecuredPasswords(DataSourceDescriptor dataSource) { dataSource.getSecurePreferences().removeNode(); } @Nullable private static String decryptPassword(String encPassword) { if (!CommonUtils.isEmpty(encPassword)) { try { encPassword = ENCRYPTOR.decrypt(encPassword); } catch (Throwable e) { // could not decrypt - use as is encPassword = null; } } return encPassword; } private void saveObjectFiler(XMLBuilder xml, String typeName, String objectID, DBSObjectFilter filter) throws IOException { xml.startElement(RegistryConstants.TAG_FILTER); xml.addAttribute(RegistryConstants.ATTR_TYPE, typeName); if (objectID != null) { xml.addAttribute(RegistryConstants.ATTR_ID, objectID); } if (!CommonUtils.isEmpty(filter.getName())) { xml.addAttribute(RegistryConstants.ATTR_NAME, filter.getName()); } if (!CommonUtils.isEmpty(filter.getDescription())) { xml.addAttribute(RegistryConstants.ATTR_DESCRIPTION, filter.getDescription()); } if (!filter.isEnabled()) { xml.addAttribute(RegistryConstants.ATTR_ENABLED, false); } for (String include : CommonUtils.safeCollection(filter.getInclude())) { xml.startElement(RegistryConstants.TAG_INCLUDE); xml.addAttribute(RegistryConstants.ATTR_NAME, include); xml.endElement(); } for (String exclude : CommonUtils.safeCollection(filter.getExclude())) { xml.startElement(RegistryConstants.TAG_EXCLUDE); xml.addAttribute(RegistryConstants.ATTR_NAME, exclude); xml.endElement(); } xml.endElement(); } @Override public IProject getProject() { return project; } private static class ParseResults { Set<DataSourceDescriptor> updatedDataSources = new HashSet<>(); Set<DataSourceDescriptor> addedDataSources = new HashSet<>(); } private class DataSourcesParser implements SAXListener { DataSourceDescriptor curDataSource; DataSourceOrigin origin; boolean refresh; boolean isDescription = false; DBRShellCommand curCommand = null; private DBWHandlerConfiguration curNetworkHandler; private DBSObjectFilter curFilter; private StringBuilder curQuery; private ParseResults parseResults; private boolean passwordReadCanceled = false; private DataSourcesParser(DataSourceOrigin origin, boolean refresh, ParseResults parseResults) { this.origin = origin; this.refresh = refresh; this.parseResults = parseResults; } @Override public void saxStartElement(SAXReader reader, String namespaceURI, String localName, Attributes atts) throws XMLException { isDescription = false; curCommand = null; switch (localName) { case RegistryConstants.TAG_FOLDER: { String name = atts.getValue(RegistryConstants.ATTR_NAME); String description = atts.getValue(RegistryConstants.ATTR_DESCRIPTION); String parentFolder = atts.getValue(RegistryConstants.ATTR_PARENT); DataSourceFolder parent = parentFolder == null ? null : findFolderByPath(parentFolder, true); DataSourceFolder folder = parent == null ? findFolderByPath(name, true) : parent.getChild(name); if (folder == null) { folder = new DataSourceFolder(DataSourceRegistry.this, parent, name, description); dataSourceFolders.add(folder); } else { folder.setDescription(description); } break; } case RegistryConstants.TAG_DATA_SOURCE: { String name = atts.getValue(RegistryConstants.ATTR_NAME); String id = atts.getValue(RegistryConstants.ATTR_ID); if (id == null) { // Support of old version without ID id = name; } String providerId = atts.getValue(RegistryConstants.ATTR_PROVIDER); DataSourceProviderDescriptor provider = DataSourceProviderRegistry.getInstance().getDataSourceProvider(providerId); if (provider == null) { log.warn("Can't find datasource provider " + providerId + " for datasource '" + name + "'"); curDataSource = null; reader.setListener(EMPTY_LISTENER); return; } String driverId = atts.getValue(RegistryConstants.ATTR_DRIVER); DriverDescriptor driver = provider.getDriver(driverId); if (driver == null) { log.warn("Can't find driver " + driverId + " in datasource provider " + provider.getId() + " for datasource '" + name + "'. Create new driver"); driver = provider.createDriver(driverId); provider.addDriver(driver); } curDataSource = getDataSource(id); boolean newDataSource = (curDataSource == null); if (newDataSource) { curDataSource = new DataSourceDescriptor( DataSourceRegistry.this, origin, id, driver, new DBPConnectionConfiguration()); } else { // Clean settings - they have to be loaded later by parser curDataSource.getConnectionConfiguration().setProperties(Collections.<String, String>emptyMap()); curDataSource.getConnectionConfiguration().setHandlers(Collections.<DBWHandlerConfiguration>emptyList()); curDataSource.clearFilters(); } curDataSource.setName(name); curDataSource.setSavePassword(CommonUtils.getBoolean(atts.getValue(RegistryConstants.ATTR_SAVE_PASSWORD))); curDataSource.setShowSystemObjects(CommonUtils.getBoolean(atts.getValue(RegistryConstants.ATTR_SHOW_SYSTEM_OBJECTS))); curDataSource.setShowUtilityObjects(CommonUtils.getBoolean(atts.getValue(RegistryConstants.ATTR_SHOW_UTIL_OBJECTS))); curDataSource.setConnectionReadOnly(CommonUtils.getBoolean(atts.getValue(RegistryConstants.ATTR_READ_ONLY))); final String folderPath = atts.getValue(RegistryConstants.ATTR_FOLDER); if (folderPath != null) { curDataSource.setFolder(findFolderByPath(folderPath, true)); } curDataSource.setLockPasswordHash(atts.getValue(RegistryConstants.ATTR_LOCK_PASSWORD)); { // Legacy filter settings String legacyCatalogFilter = atts.getValue(RegistryConstants.ATTR_FILTER_CATALOG); if (!CommonUtils.isEmpty(legacyCatalogFilter)) { curDataSource.updateObjectFilter(DBSCatalog.class.getName(), null, new DBSObjectFilter(legacyCatalogFilter, null)); } String legacySchemaFilter = atts.getValue(RegistryConstants.ATTR_FILTER_SCHEMA); if (!CommonUtils.isEmpty(legacySchemaFilter)) { curDataSource.updateObjectFilter(DBSSchema.class.getName(), null, new DBSObjectFilter(legacySchemaFilter, null)); } } if (newDataSource) { dataSources.add(curDataSource); parseResults.addedDataSources.add(curDataSource); } else { parseResults.updatedDataSources.add(curDataSource); } break; } case RegistryConstants.TAG_CONNECTION: if (curDataSource != null) { DriverDescriptor driver = curDataSource.getDriver(); if (CommonUtils.isEmpty(driver.getName())) { // Broken driver - seems to be just created driver.setName(atts.getValue(RegistryConstants.ATTR_URL)); driver.setDriverClassName("java.sql.Driver"); } DBPConnectionConfiguration config = curDataSource.getConnectionConfiguration(); config.setHostName(atts.getValue(RegistryConstants.ATTR_HOST)); config.setHostPort(atts.getValue(RegistryConstants.ATTR_PORT)); config.setServerName(atts.getValue(RegistryConstants.ATTR_SERVER)); config.setDatabaseName(atts.getValue(RegistryConstants.ATTR_DATABASE)); config.setUrl(atts.getValue(RegistryConstants.ATTR_URL)); if (!passwordReadCanceled) { final String[] creds = readSecuredCredentials(atts, curDataSource, null); config.setUserName(creds[0]); if (curDataSource.isSavePassword()) { config.setUserPassword(creds[1]); } } config.setClientHomeId(atts.getValue(RegistryConstants.ATTR_HOME)); config.setConnectionType( DataSourceProviderRegistry.getInstance().getConnectionType( CommonUtils.toString(atts.getValue(RegistryConstants.ATTR_TYPE)), DBPConnectionType.DEFAULT_TYPE) ); String colorValue = atts.getValue(RegistryConstants.ATTR_COLOR); if (!CommonUtils.isEmpty(colorValue)) { config.setConnectionColor(colorValue); } String keepAlive = atts.getValue(RegistryConstants.ATTR_KEEP_ALIVE); if (!CommonUtils.isEmpty(keepAlive)) { try { config.setKeepAliveInterval(Integer.parseInt(keepAlive)); } catch (NumberFormatException e) { log.warn("Bad keep-alive interval value", e); } } } break; case RegistryConstants.TAG_BOOTSTRAP: if (curDataSource != null) { DBPConnectionConfiguration config = curDataSource.getConnectionConfiguration(); if (atts.getValue(RegistryConstants.ATTR_AUTOCOMMIT) != null) { config.getBootstrap().setDefaultAutoCommit(CommonUtils.toBoolean(atts.getValue(RegistryConstants.ATTR_AUTOCOMMIT))); } if (atts.getValue(RegistryConstants.ATTR_TXN_ISOLATION) != null) { config.getBootstrap().setDefaultTransactionIsolation(CommonUtils.toInt(atts.getValue(RegistryConstants.ATTR_TXN_ISOLATION))); } if (!CommonUtils.isEmpty(atts.getValue(RegistryConstants.ATTR_DEFAULT_OBJECT))) { config.getBootstrap().setDefaultObjectName(atts.getValue(RegistryConstants.ATTR_DEFAULT_OBJECT)); } if (atts.getValue(RegistryConstants.ATTR_IGNORE_ERRORS) != null) { config.getBootstrap().setIgnoreErrors(CommonUtils.toBoolean(atts.getValue(RegistryConstants.ATTR_IGNORE_ERRORS))); } } break; case RegistryConstants.TAG_QUERY: curQuery = new StringBuilder(); break; case RegistryConstants.TAG_PROPERTY: if (curNetworkHandler != null) { curNetworkHandler.getProperties().put( atts.getValue(RegistryConstants.ATTR_NAME), atts.getValue(RegistryConstants.ATTR_VALUE)); } else if (curDataSource != null) { final String propName = atts.getValue(RegistryConstants.ATTR_NAME); final String propValue = atts.getValue(RegistryConstants.ATTR_VALUE); if (propName != null) { if (propName.startsWith(DBConstants.INTERNAL_PROP_PREFIX)) { // Backward compatibility - internal properties are provider properties curDataSource.getConnectionConfiguration().setProviderProperty(propName, propValue); } else { curDataSource.getConnectionConfiguration().setProperty(propName, propValue); } } } break; case RegistryConstants.TAG_PROVIDER_PROPERTY: if (curDataSource != null) { curDataSource.getConnectionConfiguration().setProviderProperty( atts.getValue(RegistryConstants.ATTR_NAME), atts.getValue(RegistryConstants.ATTR_VALUE)); } break; case RegistryConstants.TAG_EVENT: if (curDataSource != null) { DBPConnectionEventType eventType = DBPConnectionEventType.valueOf(atts.getValue(RegistryConstants.ATTR_TYPE)); curCommand = new DBRShellCommand(""); curCommand.setEnabled(CommonUtils.getBoolean(atts.getValue(RegistryConstants.ATTR_ENABLED))); curCommand.setShowProcessPanel(CommonUtils.getBoolean(atts.getValue(RegistryConstants.ATTR_SHOW_PANEL))); curCommand.setWaitProcessFinish(CommonUtils.getBoolean(atts.getValue(RegistryConstants.ATTR_WAIT_PROCESS))); if (curCommand.isWaitProcessFinish()) { String timeoutString = atts.getValue(RegistryConstants.ATTR_WAIT_PROCESS_TIMEOUT); int timeoutMs = CommonUtils.toInt(timeoutString, DBRShellCommand.WAIT_PROCESS_TIMEOUT_FOREVER); curCommand.setWaitProcessTimeoutMs(timeoutMs); } curCommand.setTerminateAtDisconnect(CommonUtils.getBoolean(atts.getValue(RegistryConstants.ATTR_TERMINATE_AT_DISCONNECT))); curDataSource.getConnectionConfiguration().setEvent(eventType, curCommand); } break; case RegistryConstants.TAG_CUSTOM_PROPERTY: if (curDataSource != null) { String propName = atts.getValue(RegistryConstants.ATTR_NAME); String propValue = atts.getValue(RegistryConstants.ATTR_VALUE); // TODO: remove bootstrap preferences later. PResent for config backward compatibility if (propName.equals(DEFAULT_AUTO_COMMIT)) { curDataSource.getConnectionConfiguration().getBootstrap().setDefaultAutoCommit(CommonUtils.toBoolean(propValue)); } else if (propName.equals(DEFAULT_ISOLATION)) { curDataSource.getConnectionConfiguration().getBootstrap().setDefaultTransactionIsolation(CommonUtils.toInt(propValue)); } else if (propName.equals(DEFAULT_ACTIVE_OBJECT)) { if (!CommonUtils.isEmpty(propValue)) { curDataSource.getConnectionConfiguration().getBootstrap().setDefaultObjectName(propValue); } } else { curDataSource.getPreferenceStore().getProperties().put(propName, propValue); } } break; case RegistryConstants.TAG_NETWORK_HANDLER: if (curDataSource != null) { String handlerId = atts.getValue(RegistryConstants.ATTR_ID); NetworkHandlerDescriptor handlerDescriptor = NetworkHandlerRegistry.getInstance().getDescriptor(handlerId); if (handlerDescriptor == null) { log.warn("Can't find network handler '" + handlerId + "'"); reader.setListener(EMPTY_LISTENER); return; } curNetworkHandler = new DBWHandlerConfiguration(handlerDescriptor, curDataSource.getDriver()); curNetworkHandler.setEnabled(CommonUtils.getBoolean(atts.getValue(RegistryConstants.ATTR_ENABLED))); curNetworkHandler.setSavePassword(CommonUtils.getBoolean(atts.getValue(RegistryConstants.ATTR_SAVE_PASSWORD))); if (!passwordReadCanceled) { final String[] creds = readSecuredCredentials(atts, curDataSource, "network/" + handlerId); curNetworkHandler.setUserName(creds[0]); if (curNetworkHandler.isSavePassword()) { curNetworkHandler.setPassword(creds[1]); } } curDataSource.getConnectionConfiguration().addHandler(curNetworkHandler); } break; case RegistryConstants.TAG_FILTER: if (curDataSource != null) { String typeName = atts.getValue(RegistryConstants.ATTR_TYPE); String objectID = atts.getValue(RegistryConstants.ATTR_ID); if (typeName != null) { curFilter = new DBSObjectFilter(); curFilter.setName(atts.getValue(RegistryConstants.ATTR_NAME)); curFilter.setDescription(atts.getValue(RegistryConstants.ATTR_DESCRIPTION)); curFilter.setEnabled(CommonUtils.getBoolean(atts.getValue(RegistryConstants.ATTR_ENABLED), true)); curDataSource.updateObjectFilter(typeName, objectID, curFilter); } } break; case RegistryConstants.TAG_INCLUDE: if (curFilter != null) { curFilter.addInclude(CommonUtils.notEmpty(atts.getValue(RegistryConstants.ATTR_NAME))); } break; case RegistryConstants.TAG_EXCLUDE: if (curFilter != null) { curFilter.addExclude(CommonUtils.notEmpty(atts.getValue(RegistryConstants.ATTR_NAME))); } break; case RegistryConstants.TAG_DESCRIPTION: isDescription = true; break; case RegistryConstants.TAG_VIRTUAL_META_DATA: if (curDataSource != null) { reader.setListener(curDataSource.getVirtualModel().getModelParser()); } break; } } @Override public void saxText(SAXReader reader, String data) throws XMLException { if (isDescription && curDataSource != null) { curDataSource.setDescription(data); } else if (curCommand != null) { curCommand.setCommand(data); curCommand = null; } else if (curQuery != null) { curQuery.append(data); } } @Override public void saxEndElement(SAXReader reader, String namespaceURI, String localName) throws XMLException { switch (localName) { case RegistryConstants.TAG_DATA_SOURCE: curDataSource = null; break; case RegistryConstants.TAG_NETWORK_HANDLER: curNetworkHandler = null; break; case RegistryConstants.TAG_FILTER: curFilter = null; break; case RegistryConstants.TAG_QUERY: if (curDataSource != null && curQuery != null && curQuery.length() > 0) { curDataSource.getConnectionConfiguration().getBootstrap().getInitQueries().add(curQuery.toString()); curQuery = null; } break; } isDescription = false; } private String[] readSecuredCredentials(Attributes xmlAttrs, DataSourceDescriptor dataSource, String subNode) { String[] creds = new String[2]; final DBASecureStorage secureStorage = getPlatform().getSecureStorage(); { try { if (secureStorage.useSecurePreferences()) { ISecurePreferences prefNode = dataSource.getSecurePreferences(); if (subNode != null) { for (String nodeName : subNode.split("/")) { prefNode = prefNode.node(nodeName); } } creds[0] = prefNode.get(RegistryConstants.ATTR_USER, null); creds[1] = prefNode.get(RegistryConstants.ATTR_PASSWORD, null); } } catch (StorageException e) { // Most likely user canceled master password enter of failed by some other reason. // Anyhow we won't try it again log.error("Can't read password from secure storage", e); passwordReadCanceled = true; } } if (CommonUtils.isEmpty(creds[0])) { creds[0] = xmlAttrs.getValue(RegistryConstants.ATTR_USER); } if (CommonUtils.isEmpty(creds[1])) { final String encPassword = xmlAttrs.getValue(RegistryConstants.ATTR_PASSWORD); creds[1] = CommonUtils.isEmpty(encPassword) ? null : decryptPassword(encPassword); } return creds; } } private class DisconnectTask implements DBRRunnableWithProgress { boolean disconnected; @Override public void run(DBRProgressMonitor monitor) throws InvocationTargetException, InterruptedException { List<DataSourceDescriptor> dsSnapshot; synchronized (dataSources) { dsSnapshot = CommonUtils.copyList(dataSources); } monitor.beginTask("Disconnect all databases", dsSnapshot.size()); try { for (DataSourceDescriptor dataSource : dsSnapshot) { if (monitor.isCanceled()) { break; } if (dataSource.isConnected()) { try { // Disconnect monitor.subTask("Disconnect from [" + dataSource.getName() + "]"); disconnected = dataSource.disconnect(monitor); } catch (Exception ex) { log.error("Can't shutdown data source '" + dataSource.getName() + "'", ex); } } monitor.worked(1); } } finally { monitor.done(); } } } }