/*
* 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.tools.compare;
import org.eclipse.core.runtime.IStatus;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBPDataSource;
import org.jkiss.dbeaver.model.DBPNamedObject;
import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor;
import org.jkiss.dbeaver.model.DBPSystemObject;
import org.jkiss.dbeaver.model.navigator.DBNDatabaseFolder;
import org.jkiss.dbeaver.model.navigator.DBNDatabaseNode;
import org.jkiss.dbeaver.model.navigator.meta.DBXTreeNode;
import org.jkiss.dbeaver.model.runtime.DBRProgressListener;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.struct.DBSObject;
import org.jkiss.dbeaver.model.struct.DBSObjectContainer;
import org.jkiss.dbeaver.runtime.properties.*;
import java.util.*;
public class CompareObjectsExecutor {
private static final Log log = Log.getLog(CompareObjectsExecutor.class);
private final Object PROPS_LOCK = new Object();
private CompareObjectsSettings settings;
private final List<DBNDatabaseNode> rootNodes;
private final Map<DBPDataSource, DataSourcePropertyFilter> dataSourceFilters = new IdentityHashMap<>();
private final DBRProgressListener initializeFinisher;
private final ILazyPropertyLoadListener lazyPropertyLoadListener;
private volatile int initializedCount = 0;
private volatile IStatus initializeError;
private final Map<Object, Map<DBPPropertyDescriptor, Object>> propertyValues = new IdentityHashMap<>();
private final List<CompareReportLine> reportLines = new ArrayList<>();
private int reportDepth = 0;
private CompareReportLine lastLine;
private void reportObjectsCompareBegin(List<DBNDatabaseNode> objects)
{
reportDepth++;
lastLine = new CompareReportLine();
lastLine.depth = reportDepth;
lastLine.structure = objects.get(0);
lastLine.nodes = new DBNDatabaseNode[rootNodes.size()];
for (int i = 0; i < rootNodes.size(); i++) {
for (DBNDatabaseNode node : objects) {
if (node == rootNodes.get(i) || node.isChildOf(rootNodes.get(i))) {
lastLine.nodes[i] = node;
break;
}
}
}
for (DBNDatabaseNode node : lastLine.nodes) {
if (node == null) {
lastLine.hasDifference = true;
break;
}
}
reportLines.add(lastLine);
}
private void reportPropertyCompare(ObjectPropertyDescriptor property)
{
CompareReportProperty reportProperty = new CompareReportProperty(property);
reportProperty.values = new Object[rootNodes.size()];
for (int i = 0; i < lastLine.nodes.length; i++) {
DBNDatabaseNode node = lastLine.nodes[i];
if (node == null) {
continue;
}
Map<DBPPropertyDescriptor, Object> valueMap = propertyValues.get(node.getObject());
if (valueMap != null) {
reportProperty.values[i] = valueMap.get(property);
}
}
if (lastLine.properties == null) {
lastLine.properties = new ArrayList<>();
}
lastLine.properties.add(reportProperty);
Object firstValue = reportProperty.values[0];
for (int i = 1; i < rootNodes.size(); i++) {
if (!CompareUtils.equalPropertyValues(reportProperty.values[i], firstValue)) {
lastLine.hasDifference = true;
break;
}
}
}
private void reportObjectsCompareEnd()
{
reportDepth--;
}
public CompareObjectsExecutor(CompareObjectsSettings settings)
{
this.settings = settings;
this.rootNodes = settings.getNodes();
initializeFinisher = new DBRProgressListener() {
@Override
public void onTaskFinished(IStatus status)
{
if (!status.isOK()) {
initializeError = status;
} else {
initializedCount++;
}
}
};
lazyPropertyLoadListener = new ILazyPropertyLoadListener() {
@Override
public void handlePropertyLoad(Object object, DBPPropertyDescriptor property, Object propertyValue, boolean completed)
{
synchronized (propertyValues) {
Map<DBPPropertyDescriptor, Object> objectProps = propertyValues.get(object);
if (objectProps != null) {
objectProps.put(property, propertyValue);
}
}
}
};
PropertiesContributor.getInstance().addLazyListener(lazyPropertyLoadListener);
}
IStatus getInitializeError()
{
return initializeError;
}
void dispose()
{
PropertiesContributor.getInstance().removeLazyListener(lazyPropertyLoadListener);
}
public CompareReport compareObjects(DBRProgressMonitor monitor, List<DBNDatabaseNode> nodes)
throws DBException, InterruptedException
{
reportLines.clear();
lastLine = null;
compareNodes(monitor, nodes);
return new CompareReport(rootNodes, reportLines);
}
private void compareNodes(DBRProgressMonitor monitor, List<DBNDatabaseNode> nodes)
throws DBException, InterruptedException
{
reportObjectsCompareBegin(nodes);
try {
if (nodes.size() > 1) {
// Go deeper only if we have more than one node
if (!settings.isCompareOnlyStructure() && !(nodes.get(0) instanceof DBNDatabaseFolder)) {
compareProperties(monitor, nodes);
}
compareChildren(monitor, nodes);
}
} finally {
reportObjectsCompareEnd();
}
}
private void compareProperties(DBRProgressMonitor monitor, List<DBNDatabaseNode> nodes) throws DBException, InterruptedException
{
// Clear compare singletons
this.initializedCount = 0;
this.initializeError = null;
this.propertyValues.clear();
StringBuilder title = new StringBuilder();
// Initialize nodes
{
monitor.subTask("Initialize nodes");
for (DBNDatabaseNode node : nodes) {
if (title.length() > 0) title.append(", ");
title.append(node.getNodeFullName());
node.initializeNode(null, initializeFinisher);
monitor.worked(1);
}
while (initializedCount != nodes.size()) {
if (initializeError != null) {
throw new DBException(initializeError.getMessage());
}
Thread.sleep(100);
if (monitor.isCanceled()) {
throw new InterruptedException();
}
}
}
monitor.subTask("Compare " + title.toString());
boolean compareLazyProperties = false;
DBNDatabaseNode firstNode = nodes.get(0);
List<ObjectPropertyDescriptor> properties = ObjectPropertyDescriptor.extractAnnotations(
null,
firstNode.getObject().getClass(),
getDataSourceFilter(firstNode));
for (ObjectPropertyDescriptor prop : properties) {
if (prop.isLazy()) {
compareLazyProperties = true;
break;
}
}
compareLazyProperties = compareLazyProperties && settings.isCompareLazyProperties();
// Load all properties
for (DBNDatabaseNode node : nodes) {
if (monitor.isCanceled()) {
throw new InterruptedException();
}
DBSObject databaseObject = node.getObject();
Map<DBPPropertyDescriptor, Object> nodeProperties = propertyValues.get(databaseObject);
if (nodeProperties == null) {
nodeProperties = new IdentityHashMap<>();
propertyValues.put(databaseObject, nodeProperties);
}
PropertyCollector propertySource = new PropertyCollector(databaseObject, compareLazyProperties);
for (ObjectPropertyDescriptor prop : properties) {
Object propertyValue = propertySource.getPropertyValue(monitor, databaseObject, prop);
synchronized (PROPS_LOCK) {
if (propertyValue instanceof DBPNamedObject) {
// Compare just object names
propertyValue = ((DBPNamedObject) propertyValue).getName();
}
nodeProperties.put(prop, propertyValue);
}
}
monitor.worked(1);
}
// Compare properties
for (ObjectPropertyDescriptor prop : properties) {
reportPropertyCompare(prop);
}
}
private void compareChildren(DBRProgressMonitor monitor, List<DBNDatabaseNode> nodes) throws DBException, InterruptedException
{
// Compare children
int nodeCount = nodes.size();
List<DBNDatabaseNode[]> allChildren = new ArrayList<>(nodeCount);
for (int i = 0; i < nodeCount; i++) {
DBNDatabaseNode node = nodes.get(i);
// Cache structure if possible
if (node.getObject() instanceof DBSObjectContainer) {
((DBSObjectContainer) node.getObject()).cacheStructure(monitor, DBSObjectContainer.STRUCT_ALL);
}
try {
DBNDatabaseNode[] children = node.getChildren(monitor);
allChildren.add(children);
} catch (Exception e) {
log.warn("Error reading child nodes for compare", e);
allChildren.add(null);
}
}
Set<String> allChildNames = new LinkedHashSet<>();
for (DBNDatabaseNode[] childList : allChildren) {
if (childList == null) continue;
for (DBNDatabaseNode child : childList) {
DBXTreeNode meta = child.getMeta();
if (meta.isVirtual()) {
// Skip virtual nodes
continue;
}
if (settings.isSkipSystemObjects() && child.getObject() instanceof DBPSystemObject && ((DBPSystemObject) child.getObject()).isSystem()) {
// Skip system objects
continue;
}
allChildNames.add(child.getNodeName());
}
}
for (String childName : allChildNames) {
int[] childIndexes = new int[nodeCount];
for (int i = 0; i < nodeCount; i++) {
childIndexes[i] = -1;
DBNDatabaseNode[] childList = allChildren.get(i);
if (childList == null) continue;
for (int k = 0; k < childList.length; k++) {
DBNDatabaseNode child = childList[k];
if (child.getNodeName().equals(childName)) {
childIndexes[i] = k;
break;
}
}
}
List<DBNDatabaseNode> nodesToCompare = new ArrayList<>(nodeCount);
for (int i = 0; i < nodeCount; i++) {
if (childIndexes[i] == -1) {
// Missing
} else {
for (int k = 0; k < nodeCount; k++) {
if (k != i && childIndexes[k] != childIndexes[i]) {
// Wrong index - add to report
break;
}
}
final DBNDatabaseNode[] childList = allChildren.get(i);
if (childList != null) {
nodesToCompare.add(childList[childIndexes[i]]);
}
}
}
// Compare children recursively
compareNodes(monitor, nodesToCompare);
}
}
private DataSourcePropertyFilter getDataSourceFilter(DBNDatabaseNode node)
{
DBPDataSource dataSource = node.getDataSourceContainer().getDataSource();
if (dataSource == null) {
return null;
}
DataSourcePropertyFilter filter = dataSourceFilters.get(dataSource);
if (filter == null) {
filter = new DataSourcePropertyFilter(dataSource);
dataSourceFilters.put(dataSource, filter);
}
return filter;
}
}