/* * 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.*; import org.eclipse.core.runtime.preferences.IEclipsePreferences; 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.core.DBeaverUI; import org.jkiss.dbeaver.model.DBPExternalFileManager; import org.jkiss.dbeaver.model.app.DBPProjectListener; import org.jkiss.dbeaver.model.app.DBPProjectManager; import org.jkiss.dbeaver.model.app.DBPResourceHandler; import org.jkiss.dbeaver.model.runtime.VoidProgressMonitor; import org.jkiss.dbeaver.ui.actions.GlobalPropertyTester; import org.jkiss.dbeaver.ui.resources.DefaultResourceHandlerImpl; import org.jkiss.dbeaver.utils.ContentUtils; import org.jkiss.dbeaver.utils.GeneralUtils; import org.jkiss.utils.CommonUtils; import java.io.*; import java.util.*; public class ProjectRegistry implements DBPProjectManager, DBPExternalFileManager { private static final Log log = Log.getLog(ProjectRegistry.class); public static final String RESOURCE_ROOT_FOLDER_NODE = "resourceRootFolder"; private static final String PROP_PROJECT_ACTIVE = "project.active"; private static final String EXT_FILES_PROPS_STORE = "dbeaver-external-files.data"; private static final Map<IProject, IEclipsePreferences> projectPreferences = new HashMap<>(); private final List<ResourceHandlerDescriptor> handlerDescriptors = new ArrayList<>(); private final Map<IProject, DataSourceRegistry> projectDatabases = new HashMap<>(); private IProject activeProject; private IWorkspace workspace; private final Map<String, Map<String, Object>> externalFileProperties = new HashMap<>(); private final List<DBPProjectListener> projectListeners = new ArrayList<>(); public ProjectRegistry(IWorkspace workspace) { this.workspace = workspace; loadExtensions(Platform.getExtensionRegistry()); loadExternalFileProperties(); } public void loadExtensions(IExtensionRegistry registry) { { IConfigurationElement[] extElements = registry.getConfigurationElementsFor(ResourceHandlerDescriptor.EXTENSION_ID); for (IConfigurationElement ext : extElements) { ResourceHandlerDescriptor handlerDescriptor = new ResourceHandlerDescriptor(ext); handlerDescriptors.add(handlerDescriptor); } } } public void loadProjects(IProgressMonitor monitor) throws DBException { final DBeaverCore core = DBeaverCore.getInstance(); String activeProjectName = DBeaverCore.getGlobalPreferenceStore().getString(PROP_PROJECT_ACTIVE); List<IProject> projects = core.getLiveProjects(); monitor.beginTask("Open active project", projects.size()); try { for (IProject project : projects) { if (project.exists() && !project.isHidden()) { if (project.getName().equals(activeProjectName)) { activeProject = project; break; } else if (activeProject == null) { // By default use first project activeProject = project; } } monitor.worked(1); } if (activeProject != null) { try { activeProject.open(monitor); activeProject.refreshLocal(IFile.DEPTH_ONE, monitor); setActiveProject(activeProject); } catch (CoreException e) { // Project seems to be corrupted projects.remove(activeProject); activeProject = null; } } } finally { monitor.done(); } if (DBeaverCore.isStandalone() && CommonUtils.isEmpty(projects)) { // Create initial project (only for standalone version) monitor.beginTask("Create general project", 1); try { activeProject = createGeneralProject(monitor); activeProject.open(monitor); setActiveProject(activeProject); } catch (CoreException e) { throw new DBException("Can't create default project", e); } finally { monitor.done(); } } } public void dispose() { if (!this.projectDatabases.isEmpty()) { log.warn("Some projects are still open: " + this.projectDatabases.keySet()); } // Dispose all DS registries for (DataSourceRegistry dataSourceRegistry : this.projectDatabases.values()) { dataSourceRegistry.dispose(); } this.projectDatabases.clear(); // Dispose resource handlers for (ResourceHandlerDescriptor handlerDescriptor : this.handlerDescriptors) { handlerDescriptor.dispose(); } this.handlerDescriptors.clear(); // Remove listeners this.workspace = null; if (!projectListeners.isEmpty()) { log.warn("Some project listeners are still register: " + projectListeners); projectListeners.clear(); } } @Override public void addProjectListener(DBPProjectListener listener) { synchronized (projectListeners) { projectListeners.add(listener); } } @Override public void removeProjectListener(DBPProjectListener listener) { synchronized (projectListeners) { projectListeners.remove(listener); } } private ResourceHandlerDescriptor getHandlerDescriptor(String id) { for (ResourceHandlerDescriptor rhd : handlerDescriptors) { if (rhd.getId().equals(id)) { return rhd; } } return null; } private ResourceHandlerDescriptor getHandlerDescriptorByRootPath(IProject project, String path) { for (ResourceHandlerDescriptor rhd : handlerDescriptors) { String defaultRoot = rhd.getDefaultRoot(project); if (defaultRoot != null && defaultRoot.equals(path)) { return rhd; } } return null; } @Override public DBPResourceHandler getResourceHandler(IResource resource) { if (resource == null || resource.isHidden() || resource.isPhantom()) { // Skip not accessible hidden and phantom resources return null; } if (resource.getParent() instanceof IProject && resource.getName().startsWith(DataSourceRegistry.CONFIG_FILE_PREFIX)) { // Skip connections settings file // TODO: remove in some older version return null; } // Check resource is synced if (resource instanceof IFile && !resource.isSynchronized(IResource.DEPTH_ZERO)) { ContentUtils.syncFile(new VoidProgressMonitor(), resource); } // Find handler DBPResourceHandler handler = null; for (ResourceHandlerDescriptor rhd : handlerDescriptors) { if (rhd.canHandle(resource)) { handler = rhd.getHandler(); break; } } if (handler == null && resource instanceof IFolder) { final IProject project = resource.getProject(); IPath relativePath = resource.getFullPath().makeRelativeTo(project.getFullPath()); while (relativePath.segmentCount() > 0) { String folderPath = relativePath.toString(); ResourceHandlerDescriptor handlerDescriptor = getHandlerDescriptorByRootPath(project, folderPath); if (handlerDescriptor != null) { handler = handlerDescriptor.getHandler(); } relativePath = relativePath.removeLastSegments(1); } } if (handler == null) { handler = DefaultResourceHandlerImpl.INSTANCE; } return handler; } public Collection<ResourceHandlerDescriptor> getResourceHandlers() { return handlerDescriptors; } public IFolder getResourceDefaultRoot(IProject project, Class<? extends DBPResourceHandler> handlerType, boolean forceCreate) { if (project == null) { return null; } for (ResourceHandlerDescriptor rhd : handlerDescriptors) { DBPResourceHandler handler = rhd.getHandler(); if (handler != null && handler.getClass() == handlerType) { final IFolder realFolder = project.getFolder(rhd.getDefaultRoot(project)); if (!realFolder.exists() && forceCreate) { try { realFolder.create(true, true, new NullProgressMonitor()); } catch (CoreException e) { log.error("Can't create '" + rhd.getName() + "' root folder '" + realFolder.getName() + "'", e); return realFolder; } } return realFolder; } } return project.getFolder(DefaultResourceHandlerImpl.DEFAULT_ROOT); } public DataSourceRegistry getDataSourceRegistry(IProject project) { if (project == null) { log.warn("No active project - can't get datasource registry"); return null; } if (!project.isOpen()) { log.warn("Project '" + project.getName() + "' is not open - can't get datasource registry"); return null; } DataSourceRegistry dataSourceRegistry = projectDatabases.get(project); if (dataSourceRegistry == null) { log.warn("Project '" + project.getName() + "' not found in registry"); } return dataSourceRegistry; } public DataSourceRegistry getActiveDataSourceRegistry() { if (activeProject == null) { return null; } final DataSourceRegistry dataSourceRegistry = projectDatabases.get(activeProject); if (dataSourceRegistry == null) { throw new IllegalStateException("No registry for active project found"); } return dataSourceRegistry; } @Override public IProject getActiveProject() { return activeProject; } public void setActiveProject(IProject project) { final IProject oldValue = this.activeProject; this.activeProject = project; DBeaverCore.getGlobalPreferenceStore().setValue(PROP_PROJECT_ACTIVE, project == null ? "" : project.getName()); GlobalPropertyTester.firePropertyChange(GlobalPropertyTester.PROP_HAS_ACTIVE_PROJECT); DBeaverUI.asyncExec(new Runnable() { @Override public void run() { DBPProjectListener[] listeners; synchronized (projectListeners) { listeners = projectListeners.toArray(new DBPProjectListener[projectListeners.size()]); } for (DBPProjectListener listener : listeners) { listener.handleActiveProjectChange(oldValue, activeProject); } } }); } private IProject createGeneralProject(IProgressMonitor monitor) throws CoreException { final String baseProjectName = DBeaverCore.isStandalone() ? "General" : "DBeaver"; String projectName = baseProjectName; for (int i = 1; ; i++) { final IProject project = workspace.getRoot().getProject(projectName); if (project.exists()) { projectName = baseProjectName + i; continue; } project.create(monitor); project.open(monitor); final IProjectDescription description = workspace.newProjectDescription(project.getName()); description.setComment("General DBeaver project"); description.setNatureIds(new String[] {DBeaverNature.NATURE_ID}); project.setDescription(description, monitor); return project; } } /** * We do not use resource listener in project registry because project should be added/removedhere * only after all other event handlers were finished and project was actually created/deleted. * Otherwise set of workspace synchronize problems occur * @param project project */ @Override public void addProject(IProject project) { if (projectDatabases.containsKey(project)) { log.warn("Project [" + project + "] already added"); return; } projectDatabases.put(project, new DataSourceRegistry(DBeaverCore.getInstance(), project)); } @Override public void removeProject(IProject project) { // Remove project from registry if (project != null) { DataSourceRegistry dataSourceRegistry = projectDatabases.get(project); if (dataSourceRegistry == null) { log.warn("Project '" + project.getName() + "' not found in the registry"); } else { dataSourceRegistry.dispose(); projectDatabases.remove(project); } } } @Override public Map<String, Object> getFileProperties(File file) { return externalFileProperties.get(file.getAbsolutePath()); } @Override public Object getFileProperty(File file, String property) { final Map<String, Object> fileProps = externalFileProperties.get(file.getAbsolutePath()); return fileProps == null ? null : fileProps.get(property); } @Override public void setFileProperty(File file, String property, Object value) { final String filePath = file.getAbsolutePath(); Map<String, Object> fileProps = externalFileProperties.get(filePath); if (fileProps == null) { fileProps = new HashMap<>(); externalFileProperties.put(filePath, fileProps); } if (value == null) { fileProps.remove(property); } else { fileProps.put(property, value); } saveExternalFileProperties(); } @Override public Map<String, Map<String, Object>> getAllFiles() { return externalFileProperties; } private void loadExternalFileProperties() { externalFileProperties.clear(); File propsFile = new File( GeneralUtils.getMetadataFolder(), EXT_FILES_PROPS_STORE); if (propsFile.exists()) { try (InputStream is = new FileInputStream(propsFile)) { try (ObjectInputStream ois = new ObjectInputStream(is)) { final Object object = ois.readObject(); if (object instanceof Map) { externalFileProperties.putAll((Map) object); } else { log.error("Bad external files properties data format: " + object); } } } catch (Exception e) { log.error("Error saving external files properties", e); } } } private void saveExternalFileProperties() { File propsFile = new File( GeneralUtils.getMetadataFolder(), EXT_FILES_PROPS_STORE); try (OutputStream os = new FileOutputStream(propsFile)) { try (ObjectOutputStream oos = new ObjectOutputStream(os)) { oos.writeObject(externalFileProperties); } } catch (Exception e) { log.error("Error saving external files properties", e); } } public static IEclipsePreferences getResourceHandlerPreferences(IProject project, String node) { IEclipsePreferences projectSettings = getProjectPreferences(project); return (IEclipsePreferences) projectSettings.node(node); } public static synchronized IEclipsePreferences getProjectPreferences(IProject project) { IEclipsePreferences preferences = projectPreferences.get(project); if (preferences == null) { preferences = new ProjectScope(project).getNode("org.jkiss.dbeaver.project.resources"); projectPreferences.put(project, preferences); } return preferences; } }