/*
* 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.jdbc.ui.actions;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.core.resources.IFile;
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.emf.ecore.resource.Resource;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.dialogs.IInputValidator;
import org.eclipse.jface.dialogs.InputDialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.progress.IProgressConstants;
import org.teiid.designer.core.ModelerCore;
import org.teiid.designer.core.workspace.ModelResource;
import org.teiid.designer.core.workspace.ModelUtil;
import org.teiid.designer.core.workspace.ModelWorkspaceException;
import org.teiid.designer.datatools.connection.IConnectionInfoHelper;
import org.teiid.designer.jdbc.JdbcSource;
import org.teiid.designer.jdbc.JdbcUtil;
import org.teiid.designer.jdbc.relational.CostAnalyzer;
import org.teiid.designer.jdbc.relational.CostAnalyzerFactory;
import org.teiid.designer.jdbc.relational.impl.TableStatistics;
import org.teiid.designer.jdbc.ui.InternalModelerJdbcUiPluginConstants;
import org.teiid.designer.jdbc.ui.ModelerJdbcUiConstants;
import org.teiid.designer.jdbc.ui.ModelerJdbcUiPlugin;
import org.teiid.designer.metamodels.relational.Table;
import org.teiid.designer.metamodels.relational.util.RelationalUtil;
import org.teiid.designer.ui.UiConstants;
import org.teiid.designer.ui.UiPlugin;
import org.teiid.designer.ui.actions.SortableSelectionAction;
import org.teiid.designer.ui.common.eventsupport.SelectionUtilities;
import org.teiid.designer.ui.editors.ModelEditorManager;
import org.teiid.designer.ui.viewsupport.ModelUtilities;
/**
* @since 8.0
*/
public class JdbcCostAnalysisAction extends SortableSelectionAction {
private IFile selectedModel;
private List<Table> selectedTables;
/**
* Constructor
*/
public JdbcCostAnalysisAction() {
super();
setImageDescriptor(ModelerJdbcUiPlugin.getDefault().getImageDescriptor(ModelerJdbcUiConstants.Images.COST_ANALYSIS));
}
/**
*
*/
@Override
public boolean isApplicable( final ISelection selection ) {
return isValidSelection(selection);
}
/**
* Valid selections include Relational Tables, Procedures or Relational Models. The roots instance variable will populated with
* all Tables and Procedures contained within the current selection.
*
* @return
* @since 4.1
*/
@Override
protected boolean isValidSelection( final ISelection selection ) {
boolean isValid = true;
this.selectedModel=null;
this.selectedTables=null;
if (SelectionUtilities.isEmptySelection(selection)) isValid = false;
if (isValid) {
try {
// Single Selection must be a Model or a Table
if (SelectionUtilities.isSingleSelection(selection)) {
Object obj = SelectionUtilities.getSelectedObject(selection);
ModelResource modelResource = null;
// Object must be a ModelResource or a Table
if (obj instanceof IFile && ModelUtilities.isModelFile((IFile)obj)) {
modelResource = ModelUtil.getModelResource((IFile)obj, false);
} else if (obj instanceof Table) {
modelResource = ModelUtil.getModel(obj);
} else {
isValid = false;
}
// Make sure the selection has a JdbcSource
if(isValid) {
if (ModelUtilities.hasJdbcSource(modelResource)) {
this.selectedModel = (IFile)modelResource.getResource();
if(obj instanceof Table) {
this.selectedTables = new ArrayList<Table>();
this.selectedTables.add((Table)obj);
}
} else {
isValid = false;
}
}
// Multi selection must be all Tables
} else {
ModelResource theModel = null;
List<Object> objs = SelectionUtilities.getSelectedObjects(selection);
List<Table> allTables = new ArrayList<Table>();
for(Object aObj : objs) {
if(aObj instanceof Table) {
ModelResource modelResource = ModelerCore.getModelWorkspace().findModelResource((Table)aObj);
if (modelResource!=null) {
if(theModel==null) theModel = modelResource;
if (!modelResource.equals(theModel)) {
isValid=false;
break;
} else {
allTables.add((Table)aObj);
}
} else {
isValid=false;
break;
}
} else {
isValid = false;
break;
}
}
// Make sure theModel has a JdbcSource
if(isValid) {
if (ModelUtilities.hasJdbcSource(theModel)) {
this.selectedModel = (IFile)theModel.getResource();
this.selectedTables = allTables;
} else {
isValid = false;
}
}
}
} catch (ModelWorkspaceException e) {
isValid = false;
}
}
return isValid;
}
/**
* We will compute column-level statistics (min-value, max-value, # of null values, # of distinct values) for all columns in
* tables in the model IFF the model is physical relational with a Jdbc source. We must first prompt the user for the
* password, as it is not stored with the Jdbc import settings.
*
* @see org.eclipse.ui.IActionDelegate#run(org.eclipse.jface.action.IAction)
* @since 4.3
*/
@Override
public void run() {
if (isEnabled()) {
final Shell shell = UiPlugin.getDefault().getCurrentWorkbenchWindow().getShell();
try {
ModelResource modelResource = ModelUtil.getModelResource(this.selectedModel, false);
if (modelResource != null) {
// JdbcSource found - check if can do costing update
String allowsCostUpdate = ModelUtil.getModelAnnotationPropertyValue(modelResource, IConnectionInfoHelper.JDBCCONNECTION_NAMESPACE+IConnectionInfoHelper.JDBCCONNECTION_ALLOW_COSTUPDATE_KEY);
if(allowsCostUpdate!=null && !allowsCostUpdate.isEmpty() && !Boolean.getBoolean(allowsCostUpdate)) {
String title = InternalModelerJdbcUiPluginConstants.Util.getString("JdbcCostAnalysisAction.costingNotAllowed.title"); //$NON-NLS-1$
String message = InternalModelerJdbcUiPluginConstants.Util.getString("JdbcCostAnalysisAction.costingNotAllowed.msg"); //$NON-NLS-1$
MessageDialog.openInformation(shell, title, message);
return;
}
boolean cancelled = openEditorIfNeeded(modelResource);
if( cancelled ) {
return;
}
final Resource resource = modelResource.getEmfResource();
executeInTransaction(resource, selectedTables, shell);
}
} catch (Exception e) {
InternalModelerJdbcUiPluginConstants.Util.log(e);
final String title = InternalModelerJdbcUiPluginConstants.Util.getString("JdbcCostAnalysisAction.errorTitle"); //$NON-NLS-1$
final String message = InternalModelerJdbcUiPluginConstants.Util.getString("JdbcCostAnalysisAction.errorMessage"); //$NON-NLS-1$
MessageDialog.openError(shell, title, message);
}
}
}
private void executeInTransaction(final Resource resource, final List<Table> tables, final Shell shell) {
boolean requiredStart = ModelerCore.startTxn(true,true,"Update Cost Statistics",this); //$NON-NLS-1$
boolean succeeded = false;
try {
internalExecute(resource, tables, shell);
succeeded = true;
} finally {
//if we started the txn, commit it.
if(requiredStart){
if(succeeded) {
ModelerCore.commitTxn();
} else {
ModelerCore.rollbackTxn();
}
}
}
}
private void internalExecute(final Resource resource, final List<Table> tables, final Shell shell) {
if (resource != null) {
final JdbcSource source = JdbcUtil.findJdbcSource(resource);
if (source != null) {
final List emfTables = getTables(tables,resource);
final CostAnalyzerFactory analyzerFactory = CostAnalyzerFactory.getCostAnalyzerFactory();
final Map tblStats = analyzerFactory.createTableInfos(emfTables);
if (tblStats != null && tblStats.size() > 0) {
CostAnalysisDialog dialog = new CostAnalysisDialog(
shell,
InternalModelerJdbcUiPluginConstants.Util.getString("JdbcCostAnalysisAction.taskDescription"), //$NON-NLS-1$
InternalModelerJdbcUiPluginConstants.Util.getString("JdbcCostAnalysisAction.passwordPrompt", new Object[] {source.getUrl(), source.getUsername()}), null, null); //$NON-NLS-1$
dialog.open();
final String password = dialog.getValue();
if (password != null) {
final Job job = new Job(
InternalModelerJdbcUiPluginConstants.Util.getString("JdbcCostAnalysisAction.jobDescription")) { //$NON-NLS-1$
@Override
protected IStatus run( IProgressMonitor monitor ) {
try {
monitor.beginTask(InternalModelerJdbcUiPluginConstants.Util.getString("JdbcCostAnalysisAction.taskDescription"), calculateNumberOfWorkIncrements(tblStats.values())); //$NON-NLS-1$
CostAnalyzer costAnalyzer = analyzerFactory.getCostAnalyzer(source,password);
// log output to standard out
// costAnalyzer.setOutputStream(System.out);
costAnalyzer.collectStatistics(tblStats, monitor);
if (!monitor.isCanceled()) {
analyzerFactory.populateEmfColumnStatistics(emfTables, tblStats);
}
monitor.done();
if (monitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
return new Status(
IStatus.OK,
ModelerJdbcUiConstants.PLUGIN_ID,
IStatus.OK,
InternalModelerJdbcUiPluginConstants.Util.getString("JdbcCostAnalysisAction.statusFinished", emfTables.size()), null); //$NON-NLS-1$
} catch (Exception e) {
InternalModelerJdbcUiPluginConstants.Util.log(e);
return new Status(
IStatus.ERROR,
ModelerJdbcUiConstants.PLUGIN_ID,
IStatus.ERROR,
InternalModelerJdbcUiPluginConstants.Util.getString("JdbcCostAnalysisAction.errorMessage"), e); //$NON-NLS-1$
} finally {
}
}
};
job.setSystem(false);
job.setUser(true);
job.setProperty(IProgressConstants.KEEP_PROPERTY, Boolean.TRUE);
// start as soon as possible
job.schedule();
}
} else {
MessageDialog.openInformation(shell,
InternalModelerJdbcUiPluginConstants.Util.getString("JdbcCostAnalysisAction.taskDescription"), InternalModelerJdbcUiPluginConstants.Util.getString("CostAnalysisAction.noValidTablesMessage")); //$NON-NLS-1$ //$NON-NLS-2$
}
}
}
}
// calculate the number of work units for the progress monitoring
// of this cost analysis task
int calculateNumberOfWorkIncrements( Collection tblStats ) {
// first, add twice the number of columns for two table operations
// (table cardinality and column attribute population)
int numWorkInc = tblStats.size() * 2;
for (Iterator it = tblStats.iterator(); it.hasNext();) {
TableStatistics tblStat = (TableStatistics)it.next();
// add the number of columns from each table,
// as each requires 1-2 database operations
numWorkInc += tblStat.getColumnStats().size();
}
return numWorkInc;
}
/**
* Get the tables for the costing updates. If tables have been selected, they're used. Otherwise all tables from the resource are used.
* @param selectedTables the list of selected tables
* @param resource the resource
* @return the tables for costing update
*/
private List<Table> getTables(List<Table> selectedTables, final Resource resource) {
List<Table> resultTables = null;
if(selectedTables!=null && !selectedTables.isEmpty()) {
resultTables = selectedTables;
} else {
resultTables = RelationalUtil.findTables(resource);
}
return resultTables;
}
class CostAnalysisDialog extends InputDialog {
private static final char ECHO_CHAR = '*';
public CostAnalysisDialog( Shell parentShell,
String dialogTitle,
String dialogMessage,
String initialValue,
IInputValidator validator ) {
super(parentShell, dialogTitle, dialogMessage, initialValue, validator);
}
@Override
protected Control createDialogArea( Composite parent ) {
Control control = super.createDialogArea(parent);
getText().setEchoChar(ECHO_CHAR);
return control;
}
}
@Override
public int compareTo( Object o ) {
if (o instanceof String) {
return getText().compareTo((String)o);
}
if (o instanceof Action) {
return getText().compareTo(((Action)o).getText());
}
return 0;
}
/**
* Should only be called if current object and model are not <code>null</code>.
*
* @since 5.5.3
*/
private boolean openEditorIfNeeded(ModelResource currentModel) {
boolean openEditorCancelled = false;
// we only need to worry about the readonly status if the file is not currently open,
// and its underlying IResource is not read only
if (currentModel!=null && !isEditorOpen(currentModel) && !currentModel.getResource().getResourceAttributes().isReadOnly()) {
final IFile modelFile = (IFile)currentModel.getResource();
Shell shell = UiPlugin.getDefault().getCurrentWorkbenchWindow().getShell();
// may want to change these text strings eventually:
if (MessageDialog.openQuestion(shell,
ModelEditorManager.OPEN_EDITOR_TITLE,
ModelEditorManager.OPEN_EDITOR_MESSAGE)) {
// load and activate, not async (to prevent multiple dialogs from coming up):
// Changed to use method that insures Object editor mode is on
ModelEditorManager.openInEditMode(modelFile, true, UiConstants.ObjectEditor.IGNORE_OPEN_EDITOR);
} else {
openEditorCancelled = true;
}
}
return openEditorCancelled;
}
private boolean isEditorOpen(ModelResource currentModel) {
if (currentModel != null) {
IFile modelFile = (IFile)currentModel.getResource();
return (ModelEditorManager.isOpen(modelFile));
}
return false;
}
}