/*
* 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.ui.controls.txn;
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.eclipse.jface.text.source.ISharedTextColors;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.*;
import org.eclipse.ui.menus.WorkbenchWindowControlContribution;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.core.DBeaverUI;
import org.jkiss.dbeaver.model.DBPContextProvider;
import org.jkiss.dbeaver.model.exec.DBCExecutionContext;
import org.jkiss.dbeaver.model.exec.DBCSavepoint;
import org.jkiss.dbeaver.model.exec.DBCStatement;
import org.jkiss.dbeaver.model.qm.QMTransactionState;
import org.jkiss.dbeaver.model.qm.QMUtils;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.runtime.DefaultProgressMonitor;
import org.jkiss.dbeaver.runtime.qm.DefaultExecutionHandler;
import org.jkiss.dbeaver.ui.IActionConstants;
import org.jkiss.dbeaver.ui.UIUtils;
import org.jkiss.dbeaver.ui.controls.querylog.QueryLogViewer;
import org.jkiss.dbeaver.ui.perspective.AbstractPartListener;
/**
* DataSource Toolbar
*/
public class TransactionMonitorToolbar {
private static final int MONITOR_UPDATE_DELAY = 250;
private static final RGB COLOR_FULL = QueryLogViewer.COLOR_LIGHT_RED;
private static final RGB COLOR_EMPTY = QueryLogViewer.COLOR_LIGHT_GREEN;
private IWorkbenchWindow workbenchWindow;
TransactionMonitorToolbar(IWorkbenchWindow workbenchWindow) {
this.workbenchWindow = workbenchWindow;
}
private Control createControl(Composite parent) {
final MonitorPanel monitorPanel = new MonitorPanel(parent);
final IPartListener partListener = new AbstractPartListener() {
@Override
public void partActivated(IWorkbenchPart part) {
monitorPanel.refresh();
}
@Override
public void partDeactivated(IWorkbenchPart part) {
monitorPanel.refresh();
}
};
final IWorkbenchPage activePage = this.workbenchWindow.getActivePage();
if (activePage != null) {
activePage.addPartListener(partListener);
monitorPanel.addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent e) {
activePage.removePartListener(partListener);
}
});
}
return monitorPanel;
}
private class RefreshJob extends Job {
private final MonitorPanel monitorPanel;
RefreshJob(MonitorPanel monitorPanel) {
super("Refresh transaction monitor");
setSystem(true);
setUser(false);
this.monitorPanel = monitorPanel;
}
@Override
protected IStatus run(final IProgressMonitor monitor) {
monitorPanel.updateTransactionsInfo(new DefaultProgressMonitor(monitor));
return Status.OK_STATUS;
}
}
private class MonitorPanel extends Composite {
private QMEventsHandler qmHandler;
private RefreshJob refreshJob;
private QMTransactionState txnState;
MonitorPanel(Composite parent) {
super(parent, SWT.BORDER);
//setBackground(parent.getDisplay().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
setCursor(parent.getDisplay().getSystemCursor(SWT.CURSOR_HAND));
addPaintListener(new PaintListener() {
@Override
public void paintControl(PaintEvent e) {
paint(e);
}
});
setBackground(parent.getDisplay().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
setToolTipText("Transactions monitor");
refreshJob = new RefreshJob(this);
qmHandler = new QMEventsHandler(this);
QMUtils.registerHandler(qmHandler);
addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent e) {
QMUtils.unregisterHandler(qmHandler);
qmHandler = null;
}
});
addMouseListener(new MouseAdapter() {
@Override
public void mouseUp(MouseEvent e) {
TransactionLogDialog.showDialog(getShell(), getActiveExecutionContext());
}
});
}
@Override
public void setToolTipText(String string) {
super.setToolTipText(string);
}
private void paint(PaintEvent e) {
Color bg;
final int updateCount = txnState == null ? 0 : txnState.getUpdateCount();
if (txnState == null || !txnState.isTransactionMode()) {
bg = getDisplay().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
} else if (updateCount == 0) {
bg = getDisplay().getSystemColor(SWT.COLOR_WHITE);
} else {
// Use gradient depending on update count
ISharedTextColors sharedColors = DBeaverUI.getSharedTextColors();
int minCount = 0, maxCount = 400;
int ratio = ((updateCount - minCount) * 100) / (maxCount - minCount);
if (updateCount >= maxCount) {
bg = sharedColors.getColor(COLOR_FULL);
} else {
final RGB rgb = UIUtils.blend(COLOR_FULL, COLOR_EMPTY, ratio);
bg = sharedColors.getColor(rgb);
}
}
Rectangle bounds = getBounds();
e.gc.setBackground(bg);
e.gc.fillRectangle(bounds.x, bounds.y, bounds.width, bounds.height);
String count;
if (txnState == null) {
count = "N/A";
} else if (!txnState.isTransactionMode()) {
count = "Auto";
} else if (updateCount > 0) {
count = String.valueOf(updateCount);
} else {
count = "None";
}
final Point textSize = e.gc.textExtent(count);
e.gc.drawText(count, bounds.x + (bounds.width - textSize.x) / 2 - 2, bounds.y + (bounds.height - textSize.y) / 2 - 1);
}
public void refresh() {
refreshJob.schedule(MONITOR_UPDATE_DELAY);
}
void updateTransactionsInfo(DBRProgressMonitor monitor) {
monitor.beginTask("Extract active transaction info", 1);
DBCExecutionContext executionContext = getActiveExecutionContext();
this.txnState = executionContext == null ? null : QMUtils.getTransactionState(executionContext);
monitor.done();
// Update UI
DBeaverUI.asyncExec(new Runnable() {
@Override
public void run() {
if (isDisposed()) {
return;
}
redraw();
updateToolTipText();
}
});
}
@Nullable
private DBCExecutionContext getActiveExecutionContext() {
if (workbenchWindow == null || workbenchWindow.getActivePage() == null) {
return null;
}
DBCExecutionContext executionContext = null;
final IEditorPart activeEditor = workbenchWindow.getActivePage().getActiveEditor();
if (activeEditor instanceof DBPContextProvider) {
executionContext = ((DBPContextProvider) activeEditor).getExecutionContext();
}
return executionContext;
}
private void updateToolTipText() {
if (txnState == null) {
setToolTipText("Not connected");
} else if (txnState.isTransactionMode()) {
final long txnUptime = txnState.getTransactionStartTime() > 0 ?
((System.currentTimeMillis() - txnState.getTransactionStartTime()) / 1000) + 1 : 0;
String toolTip = String.valueOf(txnState.getExecuteCount()) + " total statements\n" +
String.valueOf(txnState.getUpdateCount()) + " modifying statements";
if (txnUptime > 0) {
toolTip += "\n" + String.valueOf(txnUptime) + " seconds uptime";
}
setToolTipText(toolTip);
} else {
setToolTipText("Auto-commit mode");
}
}
}
public static class ToolbarContribution extends WorkbenchWindowControlContribution {
public ToolbarContribution() {
super(IActionConstants.TOOLBAR_TXN);
}
@Override
protected Control createControl(Composite parent) {
TransactionMonitorToolbar toolbar = new TransactionMonitorToolbar(DBeaverUI.getActiveWorkbenchWindow());
return toolbar.createControl(parent);
}
}
private static class QMEventsHandler extends DefaultExecutionHandler {
private final MonitorPanel monitorPanel;
QMEventsHandler(MonitorPanel monitorPanel) {
this.monitorPanel = monitorPanel;
}
@NotNull
@Override
public String getHandlerName() {
return QMEventsHandler.class.getName();
}
private void refreshMonitor() {
if (!monitorPanel.isDisposed()) {
monitorPanel.refresh();
}
}
@Override
public synchronized void handleTransactionAutocommit(@NotNull DBCExecutionContext context, boolean autoCommit) {
refreshMonitor();
}
@Override
public synchronized void handleTransactionCommit(@NotNull DBCExecutionContext context) {
refreshMonitor();
}
@Override
public synchronized void handleTransactionRollback(@NotNull DBCExecutionContext context, DBCSavepoint savepoint) {
refreshMonitor();
}
/*
@Override
public synchronized void handleStatementExecuteBegin(@NotNull DBCStatement statement) {
refreshMonitor();
}
*/
@Override
public void handleStatementExecuteEnd(@NotNull DBCStatement statement, long rows, Throwable error) {
refreshMonitor();
}
@Override
public void handleContextOpen(@NotNull DBCExecutionContext context, boolean transactional) {
refreshMonitor();
}
@Override
public void handleContextClose(@NotNull DBCExecutionContext context) {
refreshMonitor();
}
}
}