/* * 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.model.navigator; import org.eclipse.core.resources.*; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.jkiss.code.NotNull; import org.jkiss.code.Nullable; import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.Log; import org.jkiss.dbeaver.model.DBIconComposite; import org.jkiss.dbeaver.model.app.DBPPlatform; import org.jkiss.dbeaver.model.DBPImage; import org.jkiss.dbeaver.model.DBUtils; import org.jkiss.dbeaver.model.navigator.meta.DBXTreeFolder; import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor; import org.jkiss.dbeaver.model.struct.DBSObject; import org.jkiss.dbeaver.model.struct.DBSObjectState; import org.jkiss.utils.ArrayUtils; import org.jkiss.utils.CommonUtils; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * DBNModel. * Contains all objects which are shown in navigator tree. * Also ties DBSObjects to thee model (DBNNode). * * It's strongly recommended to not put the same DBSObject in tree model multiple times. * It will work but some actions will not work well * (e.g. TreeViewer sometimes update only first TreeItem corresponding to model certain model object). */ public class DBNModel implements IResourceChangeListener { private static final Log log = Log.getLog(DBNModel.class); private static class NodePath { DBNNode.NodePathType type; List<String> pathItems; public NodePath(DBNNode.NodePathType type, List<String> pathItems) { this.type = type; this.pathItems = pathItems; } public String first() { return pathItems.isEmpty() ? null : pathItems.get(0); } } private final DBPPlatform platform; private DBNRoot root; private final List<INavigatorListener> listeners = new ArrayList<>(); private transient INavigatorListener[] listenersCopy = null; private final Map<DBSObject, Object> nodeMap = new HashMap<>(); public DBNModel(DBPPlatform platform) { this.platform = platform; } public DBPPlatform getPlatform() { return platform; } public void initialize() { if (this.root != null) { throw new IllegalStateException("Can't initialize navigator model more than once"); } this.root = new DBNRoot(this); // Add all existing projects to root node for (IProject project : platform.getLiveProjects()) { root.addProject(project, false); } platform.getWorkspace().addResourceChangeListener(this); } public void dispose() { platform.getWorkspace().removeResourceChangeListener(this); this.root.dispose(false); synchronized (nodeMap) { this.nodeMap.clear(); } synchronized (this.listeners) { if (!listeners.isEmpty()) { for (INavigatorListener listener : listeners) { log.warn("Listener '" + listener + "' is not unregistered from DBM model"); } } this.listeners.clear(); this.listenersCopy = null; } this.root = null; } public DBNRoot getRoot() { return root; } @Nullable public DBNDatabaseNode findNode(DBSObject object) { if (object instanceof DBNDatabaseNode) { return (DBNDatabaseNode)object; } else { return this.getNodeByObject(object); } } @Nullable public DBNDatabaseNode getNodeByObject(DBSObject object) { if (object instanceof DBNDatabaseNode) { return (DBNDatabaseNode)object; } Object obj; synchronized (nodeMap) { obj = nodeMap.get(object); } if (obj == null) { return null; } else if (obj instanceof DBNDatabaseNode) { return (DBNDatabaseNode)obj; } else if (obj instanceof List) { @SuppressWarnings("unchecked") List<DBNDatabaseNode> nodeList = (List<DBNDatabaseNode>) obj; if (nodeList.isEmpty()) { return null; } if (nodeList.size() > 1) { for (DBNDatabaseNode node : nodeList) { if (node instanceof DBNDatabaseItem && !((DBNDatabaseItem)node).getMeta().isVirtual()) { return node; } } } // Get just first one return nodeList.get(0); } else { // Never be here throw new IllegalStateException(); } /* if (node == null) { log.warn("Can't find tree node for object " + object.getName() + " (" + object.getClass().getName() + ")"); } return node; */ } @Nullable public DBNDatabaseNode getNodeByObject(DBRProgressMonitor monitor, DBSObject object, boolean addFiltered) { DBNDatabaseNode node = getNodeByObject(object); if (node != null) { return node; } DBSObject[] path = DBUtils.getObjectPath(object, true); for (int i = 0; i < path.length - 1; i++) { DBSObject item = path[i]; DBSObject nextItem = path[i + 1]; node = getNodeByObject(item); if (node == null) { return null; } try { cacheNodeChildren(monitor, node, nextItem, addFiltered); } catch (DBException e) { log.error(e.getMessage()); return null; } } return getNodeByObject(object); } @NotNull private NodePath getNodePath(@NotNull String path) { DBNNode.NodePathType nodeType = DBNNode.NodePathType.database; for (DBNNode.NodePathType type : DBNNode.NodePathType.values()) { final String prefix = type.getPrefix(); if (path.startsWith(prefix)) { path = path.substring(prefix.length()); nodeType = type; break; } } return new NodePath(nodeType, CommonUtils.splitString(path, '/')); } @Nullable public DBNDataSource getDataSourceByPath(String path) throws DBException { String dsId = getNodePath(path).first(); for (DBNProject projectNode : getRoot().getProjects()) { DBNDataSource dataSource = projectNode.getDatabases().getDataSource(dsId); if (dataSource != null) { return dataSource; } } return null; } /** * Find node by path. * Deprecated - use getNodeByPath with project parameter * @throws DBException */ @Nullable public DBNNode getNodeByPath(@NotNull DBRProgressMonitor monitor, @NotNull String path) throws DBException { final NodePath nodePath = getNodePath(path); if (nodePath.type == DBNNode.NodePathType.database) { for (DBNProject projectNode : getRoot().getProjects()) { DBNDataSource curNode = projectNode.getDatabases().getDataSource(nodePath.first()); if (curNode != null) { return findNodeByPath(monitor, nodePath, curNode, 1); } } } else { for (DBNProject projectNode : getRoot().getProjects()) { if (projectNode.getName().equals(nodePath.first())) { return findNodeByPath(monitor, nodePath, nodePath.type == DBNNode.NodePathType.folder ? projectNode.getDatabases() : projectNode, 1); } } } return null; } @Nullable public DBNNode getNodeByPath(@NotNull DBRProgressMonitor monitor, @NotNull IProject project, @NotNull String path) throws DBException { DBNProject projectNode = getRoot().getProject(project); if (projectNode == null) { log.debug("Project node not found"); return null; } final NodePath nodePath = getNodePath(path); DBNNode curNode; switch (nodePath.type) { case database: curNode = projectNode.getDatabases().getDataSource(nodePath.first()); break; case folder: curNode = projectNode.getDatabases(); break; default: curNode = projectNode; break; } if (curNode == null) { return null; } return findNodeByPath(monitor, nodePath, curNode, 1); } public DBNResource getNodeByResource(IResource resource) { final IProject project = resource.getProject(); if (project == null) { return null; } final DBNProject projectNode = getRoot().getProject(project); if (projectNode == null) { return null; } List<IResource> path = new ArrayList<>(); for (IResource parent = resource; parent != null && parent != project; parent = parent.getParent()) { path.add(0, parent); } DBNResource curResNode = projectNode; for (IResource res : path) { curResNode = curResNode.getChild(res); if (curResNode == null) { return null; } } return curResNode; } private DBNNode findNodeByPath(DBRProgressMonitor monitor, NodePath nodePath, DBNNode curNode, int firstItem) throws DBException { for (int i = firstItem, itemsSize = nodePath.pathItems.size(); i < itemsSize; i++) { String item = nodePath.pathItems.get(i); DBNNode[] children = curNode.getChildren(monitor); DBNNode nextChild = null; if (children != null && children.length > 0) { for (DBNNode child : children) { if (nodePath.type == DBNNode.NodePathType.resource) { if (child instanceof DBNResource && ((DBNResource) child).getResource().getName().equals(item)) { nextChild = child; } } else if (nodePath.type == DBNNode.NodePathType.folder) { if (child instanceof DBNLocalFolder && child.getName().equals(item)) { nextChild = child; } } else { if (child instanceof DBNDatabaseFolder) { DBXTreeFolder meta = ((DBNDatabaseFolder) child).getMeta(); if (meta != null && !CommonUtils.isEmpty(meta.getType()) && meta.getType().equals(item)) { nextChild = child; } } if (child.getNodeName().equals(item)) { nextChild = child; } } if (nextChild != null) { if (i < itemsSize - 1) { nextChild = findNodeByPath(monitor, nodePath, nextChild, i + 1); if (nextChild != null) { return nextChild; } continue; } break; } } } curNode = nextChild; if (curNode == null) { break; } } return curNode; } private boolean cacheNodeChildren(DBRProgressMonitor monitor, DBNDatabaseNode node, DBSObject objectToCache, boolean addFiltered) throws DBException { DBNDatabaseNode[] children = node.getChildren(monitor); boolean cached = false; if (!ArrayUtils.isEmpty(children)) { for (DBNDatabaseNode child : children) { if (child instanceof DBNDatabaseFolder) { Class<?> itemsClass = ((DBNDatabaseFolder) child).getChildrenClass(); if (itemsClass != null && itemsClass.isAssignableFrom(objectToCache.getClass())) { cached = cacheNodeChildren(monitor, child, objectToCache, addFiltered); if (cached) { break; } } } } } if (!cached && addFiltered && node.isFiltered()) { // It seems this object was filtered out // As it was requested explicitly - let's add new node node.addChildItem(objectToCache); return true; } return false; } @Nullable public DBNDatabaseNode getParentNode(DBSObject object) { DBNDatabaseNode node = getNodeByObject(object); if (node != null) { if (node.getParentNode() instanceof DBNDatabaseNode) { return (DBNDatabaseNode) node.getParentNode(); } else { log.error("Object's " + object.getName() + "' parent node is not a database node: " + node.getParentNode()); return null; } } DBSObject[] path = DBUtils.getObjectPath(object, false); for (int i = 0; i < path.length; i++) { DBSObject item = path[i]; node = getNodeByObject(item); if (node == null) { // Parent node read return null; } DBNDatabaseNode[] children = node.getChildNodes(); if (ArrayUtils.isEmpty(children)) { // Parent node is not read return null; } if (item == object.getParentObject()) { // Try to find parent node withing children for (DBNDatabaseNode child : children) { if (child instanceof DBNDatabaseFolder) { Class<?> itemsClass = ((DBNDatabaseFolder) child).getChildrenClass(); if (itemsClass != null && itemsClass.isAssignableFrom(object.getClass())) { return child; } } } } } // Not found return null; } void addNode(DBNDatabaseNode node) { addNode(node, false); } void addNode(DBNDatabaseNode node, boolean reflect) { synchronized (nodeMap) { Object obj = nodeMap.get(node.getObject()); if (obj == null) { // New node nodeMap.put(node.getObject(), node); } else if (obj instanceof DBNNode) { // Second node - make a list List<DBNNode> nodeList = new ArrayList<>(2); nodeList.add((DBNNode)obj); nodeList.add(node); nodeMap.put(node.getObject(), nodeList); } else if (obj instanceof List) { // Multiple nodes @SuppressWarnings("unchecked") List<DBNNode> nodeList = (List<DBNNode>) obj; nodeList.add(node); } } if (reflect) { this.fireNodeEvent(new DBNEvent(this, DBNEvent.Action.ADD, DBNEvent.NodeChange.LOAD, node)); } } void removeNode(DBNDatabaseNode node, boolean reflect) { boolean badNode = false; synchronized (nodeMap) { Object obj = nodeMap.get(node.getObject()); if (obj == null) { // No found badNode = true; } else if (obj instanceof DBNNode) { // Just remove it if (nodeMap.remove(node.getObject()) != node) { badNode = true; } } else if (obj instanceof List) { // Multiple nodes @SuppressWarnings("unchecked") List<DBNNode> nodeList = (List<DBNNode>) obj; if (!nodeList.remove(node)) { badNode = true; } if (nodeList.isEmpty()) { nodeMap.remove(node.getObject()); } } } if (badNode) { log.warn("Remove unregistered meta node object " + node.getNodeName()); } else { if (reflect) { this.fireNodeEvent(new DBNEvent(this, DBNEvent.Action.REMOVE, DBNEvent.NodeChange.UNLOAD, node)); } } } public void addListener(INavigatorListener listener) { synchronized (this.listeners) { if (this.listeners.contains(listener)) { log.warn("Listener " + listener + " already registered in model"); } else { this.listeners.add(listener); } this.listenersCopy = this.listeners.toArray(new INavigatorListener[this.listeners.size()]); } } public void removeListener(INavigatorListener listener) { synchronized (this.listeners) { if (!this.listeners.remove(listener)) { log.warn("Listener " + listener + " wasn't registered in model"); } this.listenersCopy = this.listeners.toArray(new INavigatorListener[this.listeners.size()]); } } void fireNodeUpdate(Object source, DBNNode node, DBNEvent.NodeChange nodeChange) { this.fireNodeEvent(new DBNEvent(source, DBNEvent.Action.UPDATE, nodeChange, node)); } void fireNodeEvent(final DBNEvent event) { if (platform.isShuttingDown()) { return; } final INavigatorListener[] listenersCopy; synchronized (this.listeners) { if (listeners.isEmpty()) { return; } listenersCopy = this.listenersCopy; } if (listenersCopy.length == 0) { return; } // Notify listeners in detached job new Job("Notify node '" + event.getNode().getName() + "' changes") { { setSystem(true); } @Override protected IStatus run(IProgressMonitor monitor) { for (INavigatorListener listener : listenersCopy) { listener.nodeChanged(event); } return Status.OK_STATUS; } }.schedule(); } @Override public void resourceChanged(IResourceChangeEvent event) { if (event.getType() == IResourceChangeEvent.POST_CHANGE) { IResourceDelta delta = event.getDelta(); //IResource resource = delta.getResource(); for (IResourceDelta childDelta : delta.getAffectedChildren()) { if (childDelta.getResource() instanceof IProject) { IProject project = (IProject) childDelta.getResource(); DBNProject projectNode = getRoot().getProject(project); if (projectNode == null) { if (childDelta.getKind() == IResourceDelta.ADDED) { // New projectNode getRoot().addProject(project, true); if (platform.getProjectManager().getActiveProject() == null) { platform.getProjectManager().setActiveProject(project); } } else { // Project not found - report an error log.error("Project '" + childDelta.getResource().getName() + "' not found in navigator"); } } else { if (childDelta.getKind() == IResourceDelta.REMOVED) { // Project deleted getRoot().removeProject(project); if (project == platform.getProjectManager().getActiveProject()) { platform.getProjectManager().setActiveProject(null); } } else { if (childDelta.getFlags() == IResourceDelta.OPEN) { if (projectNode.getProject().isOpen()) { projectNode.openProject(); } else { // Project was closed - do nothing. } } else { // Some resource changed within the projectNode // Let it handle this event itself projectNode.handleResourceChange(childDelta); } } } } } } } public static synchronized DBPImage getStateOverlayImage(DBPImage image, DBSObjectState state) { if (state == null) { // Empty state return image; } final DBPImage overlayImage = state.getOverlayImage(); if (overlayImage == null) { // No overlay return image; } if (image instanceof DBIconComposite) { ((DBIconComposite) image).setBottomRight(overlayImage); return image; } return new DBIconComposite(image, false, null, null, null, overlayImage); } public static void updateConfigAndRefreshDatabases(DBNNode node) { for (DBNNode parentNode = node; parentNode != null; parentNode = parentNode.getParentNode()) { if (parentNode instanceof DBNProjectDatabases) { DBNProjectDatabases projectDatabases = (DBNProjectDatabases) parentNode; projectDatabases.getDataSourceRegistry().flushConfig(); projectDatabases.refreshChildren(); break; } } } public void ensureProjectLoaded(IProject project) { DBNProject projectNode = getRoot().getProject(project); if (projectNode != null) { projectNode.getDatabases(); } } }