/* * 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.datatools.connectivity.ui; import java.io.BufferedReader; import java.io.IOException; import java.io.Reader; import java.sql.CallableStatement; import java.sql.Connection; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.SQLXML; import java.sql.Statement; import java.sql.Types; import java.util.ArrayList; import java.util.Date; import javax.xml.transform.TransformerFactoryConfigurationError; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.datatools.sqltools.core.DatabaseIdentifier; import org.eclipse.datatools.sqltools.core.EditorCorePlugin; import org.eclipse.datatools.sqltools.editor.core.connection.IConnectionTracker; import org.eclipse.datatools.sqltools.editor.core.result.Messages; import org.eclipse.datatools.sqltools.result.OperationCommand; import org.eclipse.datatools.sqltools.result.ui.ResultsViewUIAccessor; import org.eclipse.datatools.sqltools.sqleditor.result.SimpleSQLResultRunnable; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.IViewPart; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PartInitException; import org.teiid.datatools.views.ExecutionPlanView; /** * @since 8.0 */ public class TeiidAdHocScriptRunnable extends SimpleSQLResultRunnable { // these are not shared by the base class private static int TASK_TOTAL = 100; private static int TASK_CONNECTION = 10; private static int TASK_STATEMENT = 10; private static int TASK_RUN = 50; private static int TASK_ITERATE = 30; private long _startTime = new Date().getTime(), _endTime = _startTime; private String sql; private String description; public TeiidAdHocScriptRunnable( Connection con, String description, String sql, boolean closeCon, IConnectionTracker tracker, IProgressMonitor parentMonitor, DatabaseIdentifier databaseIdentifier, ILaunchConfiguration configuration ) { super(con, sql, closeCon, tracker, parentMonitor, databaseIdentifier, configuration); this.sql = sql; this.description = description; } /* * (non-Javadoc) * * @see org.eclipse.core.internal.jobs.InternalJob#run(org.eclipse.core.runtime.IProgressMonitor) */ @Override protected IStatus run(IProgressMonitor monitor) { if (monitor == null) { monitor = new NullProgressMonitor(); } _monitor = monitor; monitor.beginTask(Messages.ResultSupportRunnable_name, TASK_TOTAL); monitor.subTask(Messages.ResultSupportRunnable_task_connection); if(_parentOperCommand == null) { resultsViewAPI.createNewInstance(getOperationCommand(), getTerminateHandler()); } else { resultsViewAPI.createSubInstance(_parentOperCommand, getOperationCommand(), getTerminateHandler()); } Connection connection = getConnection(); initConnection(connection); try { monitor.worked(TASK_CONNECTION); if (monitor.isCanceled()) { terminateExecution(); try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } return Status.CANCEL_STATUS; } monitor.subTask(Messages.ResultSupportRunnable_task_statement); try { _startTime = System.currentTimeMillis(); _stmt = prepareStatement(connection); //try-catch block is used to catch exception considering some database (avaki) can't use this method. try { boolean showPlan = Activator.getDefault().getPreferences().getBoolean(PreferenceConstants.TEIID_QUERYPLANS_ENABLED, PreferenceConstants.TEIID_QUERYPLANS_ENABLED_DEFAULT); if(showPlan) { _stmt.execute("SET SHOWPLAN DEBUG"); //$NON-NLS-1$ } _stmt.setMaxFieldSize(16384); } catch (Exception e) { //ignore } monitor.worked(TASK_STATEMENT); if (monitor.isCanceled()) { terminateExecution(); try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } return Status.CANCEL_STATUS; } monitor.subTask(Messages.ResultSupportRunnable_task_run); } catch (Throwable th) { synchronized (getOperationCommand()) { resultsViewAPI.appendThrowable(getOperationCommand(), th); resultsViewAPI.appendStatusMessage(getOperationCommand(), th.getMessage()); resultsViewAPI.updateStatus(getOperationCommand(), OperationCommand.STATUS_FAILED); } try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } return Status.CANCEL_STATUS; } boolean moreResult = false; try { moreResult = runStatement(_stmt); monitor.worked(TASK_RUN); if (monitor.isCanceled()) { terminateExecution(); try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } return Status.CANCEL_STATUS; } monitor.subTask(Messages.ResultSupportRunnable_task_iterate); } catch (Throwable th) { resultsViewAPI.appendThrowable(getOperationCommand(), th); if (th instanceof SQLException) { handleSQLException((SQLException) th); } else { synchronized (getOperationCommand()) { resultsViewAPI.appendStatusMessage(getOperationCommand(), th.getMessage()); resultsViewAPI.updateStatus(getOperationCommand(), OperationCommand.STATUS_FAILED); } } try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } return Status.CANCEL_STATUS; } // Create two threads, one is for monitoring whether user cancel execution, the other is to execute handleSuccess() method. MonitorRunnable monitorRunnable = new MonitorRunnable(); HandleSuccessJob hsJob = new HandleSuccessJob(moreResult, monitorRunnable); Thread monitorThread = new Thread(monitorRunnable); hsJob.schedule(); monitorThread.start(); // Suspend current thread before the method handleSuccess is completed and the monitor thread terminates the execution or ends normally. try { hsJob.join(); monitorThread.join(); } catch (InterruptedException e) { EditorCorePlugin.getDefault().log(e); } if(monitorRunnable._returnStatus != null) { try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } return monitorRunnable._returnStatus; } boolean success = hsJob._moreResult; //Update status in main thread to avoid deadlock. if (success) { synchronized (getOperationCommand()) { resultsViewAPI.updateStatus(getOperationCommand(), OperationCommand.STATUS_SUCCEEDED); } monitor.worked(TASK_ITERATE); } else { synchronized (getOperationCommand()) { resultsViewAPI.updateStatus(getOperationCommand(), OperationCommand.STATUS_FAILED); } try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } return Status.CANCEL_STATUS; } } finally { _endTime = System.currentTimeMillis(); resultsViewAPI.saveElapseTime(_operationCommand, _endTime - _startTime); //save the results and parameters. resultsViewAPI.saveDetailResults(_operationCommand); UpdatePlanViewRunnable upvRunnable = new UpdatePlanViewRunnable(_stmt); Display display = (Display.getCurrent() == null ? Display.getDefault() : Display.getCurrent()); if (Thread.currentThread() != display.getThread()) { display.syncExec(upvRunnable); } else { // Update the Execution Plan handleShowExecutionPlan(_stmt); // Need to land on the ResultsView handleShowResultsView(); } // Request to land on Result Tab ResultsViewUIAccessor.getInstance().showTab(_operationCommand, ResultsViewUIAccessor.RESULT_TAB); handleEnd(connection, _stmt); monitor.done(); } try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } return Status.OK_STATUS; } /* * Update the Execution Plan View */ private void handleShowExecutionPlan( Statement stmt ) { String planStr = getExecutionPlan(stmt); IWorkbenchWindow window = Activator.getDefault().getWorkbench().getActiveWorkbenchWindow(); IViewPart viewPart = null; try { if (window != null) { viewPart = window.getActivePage().showView(ExecutionPlanView.VIEW_ID); if (viewPart instanceof ExecutionPlanView) { ((ExecutionPlanView)viewPart).updateContents(this.description, this.sql, planStr); } } } catch (PartInitException e) { String message = org.teiid.datatools.connectivity.ui.Messages.getString("TeiidAdHocScriptRunnable.initViewError"); //$NON-NLS-1$ IStatus status = new Status(IStatus.ERROR, Activator.PLUGIN_ID, message, e); Activator.getDefault().getLog().log(status); } } private void handleShowResultsView() { String RESULTS_VIEW = "org.eclipse.datatools.sqltools.result.resultView"; //$NON-NLS-1$ IWorkbenchWindow window = Activator.getDefault().getWorkbench().getActiveWorkbenchWindow(); try { if (window != null) { window.getActivePage().showView(RESULTS_VIEW); } } catch (PartInitException e) { String message = org.teiid.datatools.connectivity.ui.Messages.getString("TeiidAdHocScriptRunnable.initViewError"); //$NON-NLS-1$ IStatus status = new Status(IStatus.ERROR, Activator.PLUGIN_ID, message, e); Activator.getDefault().getLog().log(status); } } private class UpdatePlanViewRunnable implements Runnable { Statement statement; public UpdatePlanViewRunnable( Statement stmt ) { this.statement = stmt; } @Override public void run() { handleShowExecutionPlan(this.statement); handleShowResultsView(); } } private class MonitorRunnable implements Runnable { // Flag expresses whether this thread should be end. volatile boolean _end = true; IStatus _returnStatus = null; @Override public void run() { if(_monitor == null) { return; } while(_end){ if (_monitor.isCanceled() || (_parentMonitor != null && _parentMonitor.isCanceled())) { getTerminateHandler().run(); _returnStatus = Status.CANCEL_STATUS; return; } try { Thread.sleep(1000); } catch (InterruptedException e) { EditorCorePlugin.getDefault().log(e); } } } } private String getExecutionPlan( Statement stmt ) { String executionPlan = null; if (stmt != null) { try { ResultSet planRs = stmt.executeQuery("SHOW PLAN"); //$NON-NLS-1$ if( planRs.next() ) { executionPlan = planRs.getString("PLAN_XML"); //$NON-NLS-1$ } planRs.close(); } catch (SQLException e) { String message = org.teiid.datatools.connectivity.ui.Messages.getString("TeiidAdHocScriptRunnable.getPlanError"); //$NON-NLS-1$ IStatus status = new Status(IStatus.ERROR, Activator.PLUGIN_ID, message, e); Activator.getDefault().getLog().log(status); } } return executionPlan; } private class HandleSuccessJob extends Job { boolean _moreResult; MonitorRunnable _monitorThread; HandleSuccessJob(boolean moreResult, MonitorRunnable monitorThread) { super(Messages.ResultSupportRunnable_handseccess_name); _moreResult = moreResult; _monitorThread = monitorThread; } @Override protected IStatus run(IProgressMonitor monitor) { monitor.beginTask(Messages.ResultSupportRunnable_handseccess_task, TASK_TOTAL); monitor.worked(TASK_STATEMENT); _moreResult = handleSuccess(_moreResult); monitor.worked(TASK_RUN + TASK_ITERATE); _monitorThread._end = false; return Status.OK_STATUS; } } @Override public void loopThroughResults(Statement cstmt, boolean moreResult) throws SQLException { boolean hasException = false;//if there are some Exception, we should thrown it out to triger finishFail boolean lastException = false;//if the last time is an Exception, we can't getResultSet immediately,should use // getMoreResult() SQLException exception = null; ResultSet theResultSet = null; ArrayList updateCountList = new ArrayList();//to keep the updateCount number temporarily while (!isTerminated() && needLoopThroughResults()) { if (isCanceled()) { terminateExecution(); throw new SQLException(Messages.ResultSupportRunnable_exception_terminated); } int updateCount = 0; if (!lastException) { try { if (moreResult) { theResultSet = cstmt.getResultSet(); if (theResultSet != null) { if(_lastUpdateCount != -1) { resultsViewAPI.appendUpdateCountMessage(getOperationCommand(), _lastUpdateCount); _lastUpdateCount = -1; } ResultSetMetaData metadata = theResultSet.getMetaData(); if(metadata.getColumnCount() == 1 && metadata.getColumnType(1) == Types.SQLXML) { try { theResultSet.next(); SQLXML xml = theResultSet.getSQLXML(1); Reader reader = xml.getCharacterStream(); BufferedReader bReader = new BufferedReader(reader); // char[] cbuf = new char[65536]; StringBuffer stringbuf = new StringBuffer(); char[] ch = new char[1]; while (bReader.read(ch) != -1) { stringbuf.append(ch); } resultsViewAPI.appendXMLResultSet(getOperationCommand(), stringbuf.toString()); if( bReader != null ) { bReader.close(); } } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (TransformerFactoryConfigurationError e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { } } else { resultsViewAPI.appendResultSet(getOperationCommand(), theResultSet); } } } updateCount = cstmt.getUpdateCount(); if (updateCount >= 0) { updateCountList.add(new Integer(updateCount)); if(_lastUpdateCount != -1) { resultsViewAPI.appendUpdateCountMessage(getOperationCommand(), _lastUpdateCount); } _lastUpdateCount = updateCount; } if (updateCount >= 0 || theResultSet != null) { /* * Notes: the code of the following line would step thread when the result set is too large. * This maybe has relations with the implementation of database driver. */ moreResult = cstmt.getMoreResults(); theResultSet.close(); theResultSet = null; continue; } break; } catch (SQLException ex) { resultsViewAPI.appendStatusMessage(getOperationCommand(), ex.getMessage()); exception = ex; hasException = true; lastException = true; boolean isClosed = getConnection() == null; if (!isClosed) { try { getConnection().getMetaData(); } catch (SQLException e) { isClosed = true; } } if (isClosed) { break; } } finally { if( theResultSet != null ) { theResultSet.close(); } } } try { moreResult = cstmt.getMoreResults(); lastException = false; } catch (SQLException ex) { //Hui Cao: when there're 2 continuous exceptions with the same SQL state, we need to break to avoid deadloop. //Although this is not a exact condition of deadloop, we have to compromise. if (ex.getSQLState() != null && exception != null && ex.getSQLState().equals(exception.getSQLState())) { break; } resultsViewAPI.appendStatusMessage(getOperationCommand(), ex.getMessage()); exception = ex; hasException = true; lastException = true; boolean isClosed = getConnection() == null; if (!isClosed) { try { getConnection().getMetaData(); } catch (SQLException e) { isClosed = true; } } if (isClosed) { break; } } } /** * The following code is to handle the updateCount. if it's a CallableStatement,we should discard the last one * because it's duplicated. From experiment we know that this is a defect of JConnect. */ int count = updateCountList.size(); if (!(cstmt instanceof CallableStatement)) { if (_lastUpdateCount != -1) { resultsViewAPI.appendUpdateCountMessage(getOperationCommand(), _lastUpdateCount); _lastUpdateCount = -1; } } else { /** * If the last two update count equal to each other, we discard the last one. Else display the last one. */ if (count < 2 || ((Integer) updateCountList.get(count - 1)).intValue() != ((Integer) updateCountList .get(count - 2)).intValue()) { if (_lastUpdateCount != -1) { resultsViewAPI.appendUpdateCountMessage(getOperationCommand(), _lastUpdateCount); _lastUpdateCount = -1; } } } //we must have these lines to throw exceptions out, //or some errors will not be displayed in result set view if (hasException) { throw exception; } } }