/*
* JBoss, Home of Professional Open Source.
*
* See the LEGAL.txt file distributed with this work for information regarding copyright ownership and licensing.
*
* See the AUTHORS.txt file distributed with this work for a full listing of individual contributors.
*/
package org.teiid.designer.runtime.ui.preview;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.eclipse.core.runtime.CoreException;
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.datatools.connectivity.IConnection;
import org.eclipse.datatools.connectivity.IConnectionFactoryProvider;
import org.eclipse.datatools.connectivity.IConnectionProfile;
import org.eclipse.datatools.connectivity.IConnectionProfileProvider;
import org.eclipse.datatools.sqltools.core.DatabaseIdentifier;
import org.eclipse.datatools.sqltools.editor.core.connection.IConnectionTracker;
import org.eclipse.datatools.sqltools.result.ui.ResultsViewUIPlugin;
import org.eclipse.datatools.sqltools.routineeditor.launching.LaunchHelper;
import org.eclipse.datatools.sqltools.routineeditor.launching.RoutineLaunchConfigurationAttribute;
import org.eclipse.datatools.sqltools.sqleditor.result.SimpleSQLResultRunnable;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.teiid.core.designer.util.I18nUtil;
import org.teiid.datatools.connectivity.ConnectivityUtil;
import org.teiid.datatools.connectivity.ui.TeiidAdHocScriptRunnable;
import org.teiid.datatools.views.ExecutionPlanView;
import org.teiid.designer.core.ModelerCore;
import org.teiid.designer.core.metamodel.aspect.sql.SqlAspectHelper;
import org.teiid.designer.core.metamodel.aspect.sql.SqlProcedureAspect;
import org.teiid.designer.core.metamodel.aspect.sql.SqlTableAspect;
import org.teiid.designer.core.workspace.ModelResource;
import org.teiid.designer.core.workspace.ModelWorkspaceException;
import org.teiid.designer.datatools.connection.ConnectionInfoHelper;
import org.teiid.designer.datatools.connection.IConnectionInfoHelper;
import org.teiid.designer.metamodels.relational.DirectionKind;
import org.teiid.designer.metamodels.relational.ProcedureParameter;
import org.teiid.designer.metamodels.webservice.Operation;
import org.teiid.designer.runtime.DqpPlugin;
import org.teiid.designer.runtime.TeiidJdbcInfo;
import org.teiid.designer.runtime.preview.PreviewManager;
import org.teiid.designer.runtime.spi.ITeiidJdbcInfo;
import org.teiid.designer.runtime.spi.ITeiidServer;
import org.teiid.designer.runtime.spi.ITeiidServerManager;
import org.teiid.designer.runtime.spi.ITeiidTranslator;
import org.teiid.designer.runtime.ui.DqpUiConstants;
import org.teiid.designer.runtime.ui.Messages;
import org.teiid.designer.runtime.ui.dialogs.AccessPatternColumnsDialog;
import org.teiid.designer.runtime.ui.dialogs.ParameterInputDialog;
import org.teiid.designer.runtime.ui.server.RuntimeAssistant;
import org.teiid.designer.ui.common.util.UiUtil;
import org.teiid.designer.ui.common.util.WidgetUtil;
import org.teiid.designer.ui.editors.ModelEditorManager;
import org.teiid.designer.ui.viewsupport.ModelIdentifier;
import org.teiid.designer.ui.viewsupport.ModelObjectUtilities;
import org.teiid.designer.ui.viewsupport.ModelUtilities;
import org.teiid.designer.webservice.util.WebServiceUtil;
/**
* @since 8.0
*/
@SuppressWarnings("restriction")
public class PreviewDataWorker {
private static final String THIS_CLASS = I18nUtil.getPropertyPrefix(PreviewDataWorker.class);
static String getString( String key ) {
return DqpUiConstants.UTIL.getString(THIS_CLASS + key);
}
static String getString( final String key, final Object param ) {
return DqpUiConstants.UTIL.getString(THIS_CLASS + key, param);
}
static String getString( final String key, final Object param, final Object param2 ) {
return DqpUiConstants.UTIL.getString(THIS_CLASS + key, param, param2);
}
private PreviewManager manager;
public PreviewDataWorker() {
super();
}
/**
* Has preview been enabled
*
* @return true if preview is possible. False otherwise
*/
public boolean isPreviewPossible() {
if( !ModelEditorManager.getDirtyResources().isEmpty() ) {
boolean doContinue = MessageDialog.openQuestion(getShell(),
getString("unsavedModelsWarning.title"), //$NON-NLS-1$
getString("unsavedModelsWarning.message")); //$NON-NLS-1$
if( !doContinue ) {
return false;
}
}
// make sure preview is enabled and that there is a Teiid Instance
if (!RuntimeAssistant.ensurePreviewEnabled(getShell())) {
return false;
}
return true;
}
/**
* Valid selections include Relational Tables, Procedures.
* @param eObject
*
* @return is previeable or not
*/
public boolean isPreviewableEObject( EObject eObject ) {
// An object can be previewed if it is of a certain object type in a Source/Relational model
// This is changed from previous releases because the requirement of having a Source binding prior to
// enablement has changed. Now the binding check is moved to the run() method which performs the check
// and queries the user for any additional info that's needed to execute the preview, including creating
// a source binding if necessary.
// eObj must be previewable
return ModelObjectUtilities.isExecutable(eObject);
}
public void run(EObject targetObject, boolean planOnly) {
IConnectionInfoHelper helper = new ConnectionInfoHelper();
ModelResource mr = ModelUtilities.getModelResourceForModelObject(targetObject);
if (mr != null && ModelIdentifier.isPhysicalModelType(mr) ) {
if( !helper.hasConnectionInfo(mr)) {
MessageDialog.openWarning(getShell(),
getString("noPreviewAvailableTitle"), //$NON-NLS-1$
getString("noProfileAvailableMissingConnectionInfoMessage", mr.getItemName())); //$NON-NLS-1$
return;
}
String translatorName = helper.getTranslatorName(mr);
if( translatorName != null ) {
ITeiidTranslator tt = null;
try {
tt = getServerManager().getDefaultServer().getTranslator(translatorName);
} catch (Exception e) {
DqpUiConstants.UTIL.log(e);
}
if( tt == null ) {
boolean result = MessageDialog.openQuestion(getShell(),
getString("noMatchingTranslatorTitle"), //$NON-NLS-1$
getString("noMatchingTeiidTranslatorMessage", translatorName, mr.getItemName())); //$NON-NLS-1$
if( !result ) {
return;
}
}
}
}
if(! validateResultDisplayProperties()) {
return;
}
manager = new PreviewManager(targetObject);
// The deploying the vdb will generate a dynamic VDB that contains the minimum dependent models, tables
// and views to run a query against.
// IStatus status = manager.deployDynamicVdb();
//
// if( status.isOK() ) {
try {
// This method will also end up undeploying the dynamic VDB, removing all traces of it
internalRun(targetObject, planOnly);
} catch (ModelWorkspaceException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// } else {
// DqpUiConstants.UTIL.log(status.getMessage());
// }
}
/**
* Open the launch configuration dialog, passing in the current workbench selection.
*
* @throws ModelWorkspaceException
*/
private void internalRun( final EObject eObject,
final boolean planOnly ) throws ModelWorkspaceException {
String sql = null;
List<String> paramValues = null;
final Shell shell = getShell();
boolean isXML = false;
@SuppressWarnings("rawtypes")
List accessPatternsColumns = null;
if (SqlAspectHelper.isTable(eObject)) {
SqlTableAspect tableAspect = (SqlTableAspect)SqlAspectHelper.getSqlAspect(eObject);
@SuppressWarnings("rawtypes")
Collection accessPatterns = tableAspect.getAccessPatterns(eObject);
if (accessPatterns != null && !accessPatterns.isEmpty()) {
// first need to type the collection since dialog requires typed collection
List<EObject> patterns = new ArrayList<EObject>(accessPatterns.size());
for (Object pattern : accessPatterns) {
patterns.add((EObject)pattern);
}
AccessPatternColumnsDialog dialog = new AccessPatternColumnsDialog(shell, patterns);
if (dialog.open() == Window.OK) {
accessPatternsColumns = dialog.getColumns();
paramValues = dialog.getColumnValues();
} else {
return;
}
} else {
paramValues = Collections.emptyList();
}
}
if (eObject instanceof Operation) {
List<EObject> inputElements = WebServiceUtil.getInputElements((Operation)eObject, false);
if (!inputElements.isEmpty()) {
ParameterInputDialog dialog = getInputDialog(inputElements);
dialog.open();
if (dialog.getReturnCode() == Window.OK) {
paramValues = dialog.getParameterValues();
sql = WebServiceUtil.getSql((Operation)eObject, paramValues);
paramValues = Collections.emptyList(); // no need to pass these to the executor
} else {
return;
}
} else {
paramValues = Collections.emptyList();
sql = WebServiceUtil.getSql((Operation)eObject, paramValues);
}
isXML = true;
} else if (SqlAspectHelper.isProcedure(eObject)) {
SqlProcedureAspect procAspect = (SqlProcedureAspect)SqlAspectHelper.getSqlAspect(eObject);
@SuppressWarnings("unchecked")
List<EObject> params = procAspect.getParameters(eObject);
// create list - (only the IN and IN/OUT parameters)
List<EObject> inParams = new ArrayList<EObject>();
for (EObject param : params) {
if (param instanceof ProcedureParameter) {
DirectionKind direction = ((ProcedureParameter)param).getDirection();
int directionKind = direction.getValue();
if (directionKind == DirectionKind.IN || directionKind == DirectionKind.INOUT) {
inParams.add(param);
}
}
}
if (!inParams.isEmpty()) {
ParameterInputDialog dialog = getInputDialog(inParams);
dialog.open();
if (dialog.getReturnCode() == Window.OK) {
paramValues = dialog.getParameterValues();
} else {
return;
}
} else {
paramValues = Collections.emptyList();
}
}
assert (paramValues != null);
final Object[] paramValuesAsArray = paramValues.toArray();
if (sql == null) {
sql = ModelObjectUtilities.getSQL(eObject, paramValuesAsArray, accessPatternsColumns);
sql = insertParameterValuesSQL(sql, paramValues);
}
if (sql == null) {
DqpUiConstants.UTIL.log(new Status(IStatus.WARNING, DqpUiConstants.PLUGIN_ID, IStatus.OK,
"failed to produce valid SQL to execute", null)); //$NON-NLS-1$
return;
}
// Check JNDI name is not null
// Check data source deployments
IStatus vdbStatus = manager.getDynamicVdbStatus();
if( vdbStatus.getSeverity() == IStatus.ERROR ) {
MessageDialog.openError(getShell(), Messages.PreviewDataWorker_previewVdbJndiMissingErrorTitle, vdbStatus.getMessage());
return;
}
IStatus dsStatus = manager.getDataSourcesStatus();
if( dsStatus.getSeverity() == IStatus.ERROR ) {
boolean result = MessageDialog.openQuestion(getShell(), Messages.PreviewDataWorker_dataSourceMissingTitle, dsStatus.getMessage());
if( result ) {
IStatus dsCreateStatus = manager.doCreateMissingDataSources();
if( dsCreateStatus.getSeverity() == IStatus.ERROR ) {
MessageDialog.openError(getShell(), Messages.PreviewDataWorker_dataSourceCreationErrorTitle, dsCreateStatus.getMessage());
return;
}
} else {
return;
}
}
String dynVdbXml = manager.getDynamicVdbStatus().getMessage();
PreviewDataInputDialog dialog = new PreviewDataInputDialog(getShell(), sql, dynVdbXml);
if( dialog.open() != Window.OK ) return;
IStatus status = manager.deployDynamicVdb();
if( status.isOK() ) {
sql = dialog.getSQL();
} else {
MessageDialog.openError(getShell(), Messages.PreviewDataWorker_vdbDeploymentErrorTitle, status.getMessage());
}
ITeiidServer defaultServer = getServerManager().getDefaultServer();
try {
String driverPath = defaultServer.getAdminDriverPath();
String vdbName = manager.getPreviewVdbName();
ITeiidJdbcInfo jdbcInfo = new TeiidJdbcInfo(vdbName, defaultServer.getTeiidJdbcInfo());
// Note that this is a Transient profile, it is not visible in
// the UI and goes away when it is garbage collected.
IConnectionProfile profile = ConnectivityUtil.createTransientTeiidProfile(defaultServer.getServerVersion(),
driverPath,
jdbcInfo.getUrl(),
jdbcInfo.getUsername(),
jdbcInfo.getPassword(),
vdbName);
final Connection sqlConnection;
IConnection connection = getSqlConnection(profile);
sqlConnection = (Connection)connection.getRawConnection();
if (null == sqlConnection || sqlConnection.isClosed()) {
final Throwable e = connection.getConnectException();
if (null != e) {
DqpUiConstants.UTIL.log(e);
} else {
DqpUiConstants.UTIL.log("Unspecified connection error"); //$NON-NLS-1$
}
MessageDialog.openError(getShell(), getString("error_getting_connection.title"), //$NON-NLS-1$
getString("error_getting_connection.message")); //$NON-NLS-1$
return;
}
DatabaseIdentifier ID = new DatabaseIdentifier(profile.getName(), vdbName);
ILaunchConfigurationWorkingCopy config = creatLaunchConfig(sql, ID);
if (planOnly) {
executePlan(sqlConnection, sql, eObject, profile);
return;
}
String labelStr = null;
if (isXML) {
labelStr = getString("previewWithPlanFor.label") + " " + ModelerCore.getModelEditor().getName(eObject); //$NON-NLS-1$ //$NON-NLS-2$
} else {
labelStr = getString("previewWithPlanFor.label") + " " + ModelerCore.getModelEditor().getName(eObject); //$NON-NLS-1$ //$NON-NLS-2$
}
String finalSQL = dialog.getSQL();
// This runnable executes the SQL and displays the results
// in the DTP 'SQL Results' view.
executeSQLResultRunnable(sqlConnection, labelStr, finalSQL, ID, config, profile, manager);
} catch (Exception e) {
DqpUiConstants.UTIL.log(IStatus.ERROR, e.getMessage());
}
}
/**
* Execute the preview.
*
* This is performed in a {@link Job} to ensure that the
* UI remains unblocked.
*
* @param sqlConnection
* @param labelStr
* @param sql
* @param iD
* @param config
* @param profile
*/
private void executeSQLResultRunnable(final Connection sqlConnection,
final String labelStr,
final String sql,
final DatabaseIdentifier ID,
final ILaunchConfigurationWorkingCopy config,
final IConnectionProfile profile,
final PreviewManager previewManager) {
Job job = new Job(labelStr + " ...") { //$NON-NLS-1$
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
SimpleSQLResultRunnable runnable = new CleanUpRunnable(sqlConnection, labelStr, sql, true, null,
monitor, ID, config, previewManager);
runnable.run();
} finally {
try {
sqlConnection.close();
} catch (Exception ex) {
}
ConnectivityUtil.deleteTransientTeiidProfile(profile);
}
return Status.OK_STATUS;
}
};
job.setPriority(Job.LONG);
job.setUser(true);
job.schedule();
}
/**
* Retrieve the execution plan. This is performed by a {@link Job}
* to ensure that the UI does not freeze.
*
* @param sqlConnection
* @param sql
* @param eObject
* @param profile
*/
private void executePlan( final Connection sqlConnection,
final String sql,
final EObject eObject,
final IConnectionProfile profile) {
Job job = new Job(DqpUiConstants.UTIL.getString(THIS_CLASS + "RetrieveExecutionPlanJob")) { //$NON-NLS-1$
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
final String planStr = getExecutionPlan(sqlConnection, sql);
Display.getDefault().asyncExec(new Runnable() {
@Override
public void run() {
IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
IViewPart viewPart = null;
try {
if (window != null) {
viewPart = window.getActivePage().showView(ExecutionPlanView.VIEW_ID);
if (viewPart instanceof ExecutionPlanView) {
String labelStr = getString("planOnlyFetchFor.label") + " " + ModelerCore.getModelEditor().getName(eObject); //$NON-NLS-1$ //$NON-NLS-2$
((ExecutionPlanView)viewPart).updateContents(labelStr, sql, planStr);
}
}
} catch (PartInitException e) {
DqpUiConstants.UTIL.log(e);
WidgetUtil.showError(e.getLocalizedMessage());
}
}
});
} catch (final SQLException ex) {
Display.getDefault().asyncExec(new Runnable() {
@Override
public void run() {
// Error Generating the plan. Log the error and display dialog.
DqpUiConstants.UTIL.log(ex);
MessageDialog.openError(getShell(), getString("errorGeneratingExecutionPlan.title"), //$NON-NLS-1$
getString("errorGeneratingExecutionPlan.message")); //$NON-NLS-1$
}
});
} finally {
try {
sqlConnection.close();
} catch (Exception ex) {
}
ConnectivityUtil.deleteTransientTeiidProfile(profile);
}
return Status.OK_STATUS;
}
};
job.setPriority(Job.LONG);
job.setUser(true);
job.schedule();
}
private String getExecutionPlan( Connection sqlConnection, String sql ) throws SQLException {
String executionPlan = null;
Statement stmt = sqlConnection.createStatement();
stmt.execute("SET SHOWPLAN DEBUG"); //$NON-NLS-1$
stmt.executeQuery(sql);
ResultSet planRs = stmt.executeQuery("SHOW PLAN"); //$NON-NLS-1$
planRs.next();
executionPlan = planRs.getString("PLAN_XML"); //$NON-NLS-1$
try {
stmt.close();
} catch (SQLException e) {
DqpPlugin.Util.log(e);
}
try {
planRs.close();
} catch (SQLException e) {
DqpPlugin.Util.log(e);
}
return executionPlan;
}
private ParameterInputDialog getInputDialog( List<EObject> params ) {
ParameterInputDialog dialog = new ParameterInputDialog(getShell(), params);
return dialog;
}
private String insertParameterValuesSQL(String sql, List<String> paramValues) {
if( paramValues != null && !paramValues.isEmpty() ) {
for (String value : paramValues) {
// If value is null, replace with the word null
if (value == null) {
sql = sql.replaceFirst("\\?", "null"); //$NON-NLS-1$ //$NON-NLS-2$
// Escaped literal - no quotes
} else if (value.trim().startsWith("{") && value.trim().endsWith("}")) { //$NON-NLS-1$ //$NON-NLS-2$
sql = sql.replaceFirst("\\?", value); //$NON-NLS-1$
// Non-null, replace with quoted value
} else {
sql = sql.replaceFirst("\\?", "'" + value + "'"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
}
}
return sql;
}
private boolean validateResultDisplayProperties() {
IPreferenceStore store = ResultsViewUIPlugin.getDefault().getPreferenceStore();
if(!store.getBoolean("org.eclipse.datatools.sqltools.result.ResultsFilterDialog.unknownProfile")){ //$NON-NLS-1$
boolean isOK = MessageDialog.openQuestion(getShell(),
getString("propertyPromptTitle"), //$NON-NLS-1$
getString("propertyPrompt")); //$NON-NLS-1$
if( isOK ) {
store.setValue("org.eclipse.datatools.sqltools.result.ResultsFilterDialog.unknownProfile", "true"); //$NON-NLS-1$ //$NON-NLS-2$
return true;
}
return false;
}
return true;
}
private ITeiidServerManager getServerManager() {
return DqpPlugin.getInstance().getServerManager();
}
protected Shell getShell() {
return UiUtil.getWorkbenchShellOnlyIfUiThread();
}
/**
* @param sql
* @param ID
* @return
* @throws CoreException
*/
private ILaunchConfigurationWorkingCopy creatLaunchConfig( String sql,
DatabaseIdentifier ID ) throws CoreException {
ILaunchConfigurationWorkingCopy config = LaunchHelper.createExternalClientConfiguration(ID, "pvdb"); //$NON-NLS-1$
config.setAttribute(RoutineLaunchConfigurationAttribute.ROUTINE_LAUNCH_SQL, sql);
// ROUTINE_LAUNCH_TYPE 3 is ad-hoc SQL
config.setAttribute(RoutineLaunchConfigurationAttribute.ROUTINE_LAUNCH_TYPE, 3);
return config;
}
private IConnection getSqlConnection( IConnectionProfile profile ) {
IConnectionProfileProvider provider = profile.getProvider();
IConnectionFactoryProvider factory = provider.getConnectionFactory("java.sql.Connection"); //$NON-NLS-1$
final String factoryId = factory.getId();
return profile.createConnection(factoryId);
}
class PreviewUnavailableDialog extends MessageDialog {
boolean openProgressView = false;
public PreviewUnavailableDialog( Shell parent ) {
super(parent, getString("previewUnavailableDialog.title"), null, //$NON-NLS-1$
getString("previewUnavailableDialog.message"), //$NON-NLS-1$
MessageDialog.INFORMATION, new String[] { IDialogConstants.OK_LABEL }, 0);
setShellStyle(getShellStyle() | SWT.RESIZE);
}
/**
* {@inheritDoc}
*
* @see org.eclipse.jface.dialogs.MessageDialog#createCustomArea(org.eclipse.swt.widgets.Composite)
*/
@Override
protected Control createCustomArea( Composite parent ) {
Composite panel = new Composite(parent, SWT.NONE);
panel.setLayout(new GridLayout(2, false));
panel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
Button btnOpenProgressView = new Button(panel, SWT.CHECK);
btnOpenProgressView.setText(getString("previewUnavailableDialog.btnShowProgressView.text")); //$NON-NLS-1$
btnOpenProgressView.setToolTipText(getString("previewUnavailableDialog.btnShowProgressView.toolTip")); //$NON-NLS-1$
btnOpenProgressView.addSelectionListener(new SelectionAdapter() {
/**
* {@inheritDoc}
*
* @see org.eclipse.swt.events.SelectionAdapter#widgetSelected(org.eclipse.swt.events.SelectionEvent)
*/
@Override
public void widgetSelected( SelectionEvent e ) {
setOpenProgressView(((Button)e.widget).getSelection());
}
});
return panel;
}
void setOpenProgressView( boolean openProgressView ) {
this.openProgressView = openProgressView;
}
public boolean shouldOpenProgressView() {
return this.openProgressView;
}
}
class CleanUpRunnable extends TeiidAdHocScriptRunnable {
final PreviewManager previewManager;
public CleanUpRunnable(Connection con, String description, String sql, boolean closeCon,
IConnectionTracker tracker, IProgressMonitor parentMonitor, DatabaseIdentifier databaseIdentifier,
ILaunchConfiguration configuration, final PreviewManager previewManager) {
super(con, description, sql, closeCon, tracker, parentMonitor, databaseIdentifier, configuration);
this.previewManager = previewManager;
}
@Override
protected IStatus run(IProgressMonitor monitor) {
IStatus status = super.run(monitor);
previewManager.undeployDynamicVdb();
return status;
}
}
}