/*
* 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.actions.datasource;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.osgi.util.NLS;
import org.eclipse.ui.ISaveablePart;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBeaverPreferences;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.core.CoreMessages;
import org.jkiss.dbeaver.core.DBeaverUI;
import org.jkiss.dbeaver.model.*;
import org.jkiss.dbeaver.model.access.DBAAuthInfo;
import org.jkiss.dbeaver.model.exec.*;
import org.jkiss.dbeaver.model.net.DBWHandlerConfiguration;
import org.jkiss.dbeaver.model.qm.QMUtils;
import org.jkiss.dbeaver.model.runtime.DBRProgressListener;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.runtime.DBRRunnableWithProgress;
import org.jkiss.dbeaver.model.runtime.VoidProgressMonitor;
import org.jkiss.dbeaver.registry.DataSourceDescriptor;
import org.jkiss.dbeaver.runtime.jobs.ConnectJob;
import org.jkiss.dbeaver.runtime.jobs.DisconnectJob;
import org.jkiss.dbeaver.runtime.ui.DBUserInterface;
import org.jkiss.dbeaver.ui.UITask;
import org.jkiss.dbeaver.ui.UIUtils;
import org.jkiss.dbeaver.ui.dialogs.ConfirmationDialog;
import org.jkiss.dbeaver.ui.editors.entity.handlers.SaveChangesHandler;
import org.jkiss.dbeaver.utils.RuntimeUtils;
import org.jkiss.utils.ArrayUtils;
import java.lang.reflect.InvocationTargetException;
public class DataSourceHandler
{
private static final Log log = Log.getLog(DataSourceHandler.class);
public static final int END_TRANSACTION_WAIT_TIME = 3000;
/**
* Connects datasource
* @param monitor progress monitor or null. If nul then new job will be started
* @param dataSourceContainer container to connect
* @param onFinish finish handler
*/
public static void connectToDataSource(
@Nullable DBRProgressMonitor monitor,
@NotNull DBPDataSourceContainer dataSourceContainer,
@Nullable final DBRProgressListener onFinish)
{
if (dataSourceContainer instanceof DataSourceDescriptor && !dataSourceContainer.isConnected()) {
final DataSourceDescriptor dataSourceDescriptor = (DataSourceDescriptor)dataSourceContainer;
if (!ArrayUtils.isEmpty(Job.getJobManager().find(dataSourceDescriptor))) {
// Already connecting/disconnecting - just return
return;
}
final ConnectJob connectJob = new ConnectJob(dataSourceDescriptor);
final JobChangeAdapter jobChangeAdapter = new JobChangeAdapter() {
@Override
public void done(IJobChangeEvent event)
{
IStatus result = connectJob.getConnectStatus();
if (result.isOK()) {
if (!dataSourceDescriptor.isSavePassword()) {
// Rest password back to null
// TODO: to be correct we need to reset password info.
// but we need a password to open isolated contexts (e.g. for data export)
// Currently it is not possible to ask for password from isolation context opening
// procedure. We need to do something here...
//dataSourceDescriptor.getConnectionConfiguration().setUserName(oldName);
//dataSourceDescriptor.getConnectionConfiguration().setUserPassword(oldPassword);
}
}
if (onFinish != null) {
onFinish.onTaskFinished(result);
} else if (!result.isOK()) {
DBUserInterface.getInstance().showError(
connectJob.getName(),
null,//NLS.bind(CoreMessages.runtime_jobs_connect_status_error, dataSourceContainer.getName()),
result);
}
}
};
if (monitor != null) {
connectJob.runSync(monitor);
jobChangeAdapter.done(new IJobChangeEvent() {
@Override
public long getDelay() {
return 0;
}
@Override
public Job getJob() {
return connectJob;
}
@Override
public IStatus getResult() {
return connectJob.getConnectStatus();
}
public IStatus getJobGroupResult() {
return null;
}
});
} else {
connectJob.addJobChangeListener(jobChangeAdapter);
// Schedule in UI because connect may be initiated during application startup
// and UI is still not initiated. In this case no progress dialog will appear
// to be sure run in UI async
DBeaverUI.asyncExec(new Runnable() {
@Override
public void run() {
connectJob.schedule();
}
});
}
}
}
public static void updateDataSourceObject(DataSourceDescriptor dataSourceDescriptor)
{
dataSourceDescriptor.getRegistry().notifyDataSourceListeners(new DBPEvent(
DBPEvent.Action.OBJECT_UPDATE,
dataSourceDescriptor,
false));
}
public static boolean askForPassword(@NotNull final DataSourceDescriptor dataSourceContainer, @Nullable final DBWHandlerConfiguration networkHandler, final boolean passwordOnly)
{
final String prompt = networkHandler != null ?
NLS.bind(CoreMessages.dialog_connection_auth_title_for_handler, networkHandler.getTitle()) :
"'" + dataSourceContainer.getName() + CoreMessages.dialog_connection_auth_title; //$NON-NLS-1$
final String user = networkHandler != null ? networkHandler.getUserName() : dataSourceContainer.getConnectionConfiguration().getUserName();
final String password = networkHandler != null ? networkHandler.getPassword() : dataSourceContainer.getConnectionConfiguration().getUserPassword();
DBAAuthInfo authInfo = new UITask<DBAAuthInfo>() {
@Override
protected DBAAuthInfo runTask() {
return DBUserInterface.getInstance().promptUserCredentials(prompt, user, password, passwordOnly);
}
}.execute();
if (authInfo == null) {
return false;
}
if (networkHandler != null) {
networkHandler.setUserName(authInfo.getUserName());
networkHandler.setPassword(authInfo.getUserPassword());
networkHandler.setSavePassword(authInfo.isSavePassword());
} else {
dataSourceContainer.getConnectionConfiguration().setUserName(authInfo.getUserName());
dataSourceContainer.getConnectionConfiguration().setUserPassword(authInfo.getUserPassword());
dataSourceContainer.setSavePassword(authInfo.isSavePassword());
}
if (authInfo.isSavePassword()) {
// Update connection properties
dataSourceContainer.getRegistry().updateDataSource(dataSourceContainer);
}
return true;
}
public static void disconnectDataSource(DBPDataSourceContainer dataSourceContainer, @Nullable final Runnable onFinish) {
// Save users
for (DBPDataSourceUser user : dataSourceContainer.getUsers()) {
if (user instanceof ISaveablePart) {
if (!SaveChangesHandler.validateAndSave(new VoidProgressMonitor(), (ISaveablePart) user)) {
return;
}
}
}
if (!checkAndCloseActiveTransaction(dataSourceContainer)) {
return;
}
if (dataSourceContainer instanceof DataSourceDescriptor && dataSourceContainer.isConnected()) {
final DataSourceDescriptor dataSourceDescriptor = (DataSourceDescriptor)dataSourceContainer;
if (!ArrayUtils.isEmpty(Job.getJobManager().find(dataSourceDescriptor))) {
// Already connecting/disconnecting - just return
return;
}
final DisconnectJob disconnectJob = new DisconnectJob(dataSourceDescriptor);
disconnectJob.addJobChangeListener(new JobChangeAdapter() {
@Override
public void done(IJobChangeEvent event)
{
IStatus result = disconnectJob.getConnectStatus();
if (onFinish != null) {
onFinish.run();
} else if (!result.isOK()) {
UIUtils.showErrorDialog(
null,
disconnectJob.getName(),
null,
result);
}
}
});
// Run in UI thread to update actions (some Eclipse magic)
DBeaverUI.asyncExec(new Runnable() {
@Override
public void run() {
disconnectJob.schedule();
}
});
}
}
public static void reconnectDataSource(final DBRProgressMonitor monitor, final DBPDataSourceContainer dataSourceContainer) {
disconnectDataSource(dataSourceContainer, new Runnable() {
@Override
public void run() {
connectToDataSource(monitor, dataSourceContainer, null);
}
});
}
public static boolean checkAndCloseActiveTransaction(DBPDataSourceContainer container) {
DBPDataSource dataSource = container.getDataSource();
if (dataSource == null) {
return true;
}
return checkAndCloseActiveTransaction(dataSource.getAllContexts());
}
public static boolean checkAndCloseActiveTransaction(DBCExecutionContext[] contexts)
{
if (contexts == null) {
return true;
}
Boolean commitTxn = null;
for (final DBCExecutionContext context : contexts) {
// First rollback active transaction
try {
if (QMUtils.isTransactionActive(context)) {
if (commitTxn == null) {
// Ask for confirmation
TransactionCloseConfirmer closeConfirmer = new TransactionCloseConfirmer(context.getDataSource().getContainer().getName());
DBeaverUI.syncExec(closeConfirmer);
switch (closeConfirmer.result) {
case IDialogConstants.YES_ID:
commitTxn = true;
break;
case IDialogConstants.NO_ID:
commitTxn = false;
break;
default:
return false;
}
}
final boolean commit = commitTxn;
DBeaverUI.runInProgressService(new DBRRunnableWithProgress() {
@Override
public void run(DBRProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
closeActiveTransaction(monitor, context, commit);
}
});
}
} catch (Throwable e) {
log.warn("Can't rollback active transaction before disconnect", e);
}
}
return true;
}
/*
public static int checkActiveTransaction(DBCExecutionContext context)
{
// First rollback active transaction
if (isContextTransactionAffected(context)) {
// Ask for confirmation
TransactionCloseConfirmer closeConfirmer = new TransactionCloseConfirmer(context.getDataSource().getContainer().getName());
DBeaverUI.syncExec(closeConfirmer);
switch (closeConfirmer.result) {
case IDialogConstants.YES_ID:
return ISaveablePart2.YES;
case IDialogConstants.NO_ID:
return ISaveablePart2.NO;
default:
return ISaveablePart2.CANCEL;
}
}
return ISaveablePart2.YES;
}
*/
public static void closeActiveTransaction(DBRProgressMonitor monitor, DBCExecutionContext context, boolean commitTxn) {
try (DBCSession session = context.openSession(monitor, DBCExecutionPurpose.UTIL, "End active transaction")) {
monitor.subTask("End active transaction");
EndTransactionTask task = new EndTransactionTask(session, commitTxn);
RuntimeUtils.runTask(task, "Close active transactions", END_TRANSACTION_WAIT_TIME);
}
}
private static class EndTransactionTask implements DBRRunnableWithProgress {
private final DBCSession session;
private final boolean commit;
private EndTransactionTask(DBCSession session, boolean commit) {
this.session = session;
this.commit = commit;
}
@Override
public void run(DBRProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
DBCTransactionManager txnManager = DBUtils.getTransactionManager(session.getExecutionContext());
if (txnManager != null) {
try {
if (commit) {
txnManager.commit(session);
} else {
txnManager.rollback(session, null);
}
} catch (DBCException e) {
throw new InvocationTargetException(e);
}
}
}
}
private static class TransactionCloseConfirmer implements Runnable {
final String name;
int result = IDialogConstants.NO_ID;
private TransactionCloseConfirmer(String name) {
this.name = name;
}
@Override
public void run()
{
result = ConfirmationDialog.showConfirmDialog(
null,
DBeaverPreferences.CONFIRM_TXN_DISCONNECT,
ConfirmationDialog.QUESTION_WITH_CANCEL,
name);
}
}
}