package rocks.inspectit.ui.rcp.editor.tree.input; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.collections.CollectionUtils; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.jface.viewers.AbstractTreeViewer; import org.eclipse.jface.viewers.ArrayContentProvider; import org.eclipse.jface.viewers.DoubleClickEvent; import org.eclipse.jface.viewers.IBaseLabelProvider; import org.eclipse.jface.viewers.IContentProvider; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.StyledString; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.TreeViewerColumn; import org.eclipse.jface.viewers.ViewerComparator; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PlatformUI; import rocks.inspectit.shared.all.cmr.service.ICachedDataService; import rocks.inspectit.shared.all.communication.DefaultData; import rocks.inspectit.shared.all.communication.data.SqlStatementData; import rocks.inspectit.shared.cs.cmr.service.ISqlDataAccessService; import rocks.inspectit.shared.cs.communication.comparator.IDataComparator; import rocks.inspectit.shared.cs.communication.comparator.InvocationAwareDataComparatorEnum; import rocks.inspectit.shared.cs.communication.comparator.ResultComparator; import rocks.inspectit.shared.cs.communication.comparator.SqlStatementDataComparatorEnum; import rocks.inspectit.shared.cs.communication.comparator.TimerDataComparatorEnum; import rocks.inspectit.ui.rcp.InspectIT; import rocks.inspectit.ui.rcp.InspectITImages; import rocks.inspectit.ui.rcp.editor.inputdefinition.InputDefinition; import rocks.inspectit.ui.rcp.editor.inputdefinition.extra.InputDefinitionExtrasMarkerFactory; import rocks.inspectit.ui.rcp.editor.inputdefinition.extra.SqlStatementInputDefinitionExtra; import rocks.inspectit.ui.rcp.editor.preferences.PreferenceEventCallback.PreferenceEvent; import rocks.inspectit.ui.rcp.editor.preferences.PreferenceId; import rocks.inspectit.ui.rcp.editor.preferences.PreferenceId.LiveMode; import rocks.inspectit.ui.rcp.editor.root.IRootEditor; import rocks.inspectit.ui.rcp.editor.text.input.SqlStatementTextInputController.SqlHolderHelper; import rocks.inspectit.ui.rcp.editor.tree.TreeViewerComparator; import rocks.inspectit.ui.rcp.editor.tree.util.DatabaseSqlTreeComparator; import rocks.inspectit.ui.rcp.editor.viewers.StyledCellIndexLabelProvider; import rocks.inspectit.ui.rcp.formatter.NumberFormatter; import rocks.inspectit.ui.rcp.formatter.TextFormatter; import rocks.inspectit.ui.rcp.preferences.PreferencesConstants; import rocks.inspectit.ui.rcp.preferences.PreferencesUtils; import rocks.inspectit.ui.rcp.repository.CmrRepositoryDefinition; import rocks.inspectit.ui.rcp.util.data.DatabaseInfoHelper; /** * This input controller displays the contents of {@link SqlStatementData} objects. * * @author Patrice Bouillet * */ public class SqlInputController extends AbstractTreeInputController { /** * The ID of this subview / controller. */ public static final String ID = "inspectit.subview.tree.sql"; /** * Empty {@link StyledString}. */ private static final StyledString EMPTY_STYLED_STRING = new StyledString(""); /** * The private inner enumeration used to define the used IDs which are mapped into the columns. * The order in this enumeration represents the order of the columns. If it is reordered, * nothing else has to be changed. * * @author Patrice Bouillet * */ private static enum Column { /** The column containing the name of the database. */ DATABASE_URL("Database URL", 120, null, null), /** The statement column. */ STATEMENT("Statement", 600, InspectITImages.IMG_DATABASE, SqlStatementDataComparatorEnum.SQL), /** Invocation Affiliation. */ INVOCATION_AFFILLIATION("In Invocations", 120, InspectITImages.IMG_INVOCATION, InvocationAwareDataComparatorEnum.INVOCATION_AFFILIATION), /** The count column. */ COUNT("Count", 80, null, TimerDataComparatorEnum.COUNT), /** The average column. */ AVERAGE("Avg (ms)", 80, null, TimerDataComparatorEnum.AVERAGE), /** The min column. */ MIN("Min (ms)", 80, null, TimerDataComparatorEnum.MIN), /** The max column. */ MAX("Max (ms)", 80, null, TimerDataComparatorEnum.MAX), /** The duration column. */ DURATION("Duration (ms)", 80, null, TimerDataComparatorEnum.DURATION), /** The prepared column. */ PREPARED("Prepared?", 80, null, SqlStatementDataComparatorEnum.IS_PREPARED_STATEMENT); /** The name. */ private String name; /** The width of the column. */ private int width; /** The image descriptor. Can be <code>null</code> */ private Image image; /** Comparator for the column. */ private IDataComparator<? super SqlStatementData> dataComparator; /** * Default constructor which creates a column enumeration object. * * @param name * The name of the column. * @param width * The width of the column. * @param imageName * The name of the image. Names are defined in {@link InspectITImages}. * @param dataComparator * Comparator for the column. */ private Column(String name, int width, String imageName, IDataComparator<? super SqlStatementData> dataComparator) { this.name = name; this.width = width; this.image = InspectIT.getDefault().getImage(imageName); this.dataComparator = dataComparator; } /** * Converts an ordinal into a column. * * @param i * The ordinal. * @return The appropriate column. */ public static Column fromOrd(int i) { if ((i < 0) || (i >= Column.values().length)) { throw new IndexOutOfBoundsException("Invalid ordinal"); } return Column.values()[i]; } } /** * The template which is send to the Repository to retrieve the actual data. */ private SqlStatementData template; /** * Input map. */ private Map<DatabaseInfoHelper, List<SqlStatementData>> inputMap = new HashMap<>(); /** * The data access service to access the data on the CMR. */ private ISqlDataAccessService dataAccessService; /** * The cached service is needed because of the ID mappings. */ private ICachedDataService cachedDataService; /** * Date to display invocations from. */ private Date fromDate = null; /** * Date to display invocations to. */ private Date toDate = null; /** * Are we in live mode. */ private boolean autoUpdate = LiveMode.ACTIVE_DEFAULT; /** * Decimal places. */ private int timeDecimalPlaces = PreferencesUtils.getIntValue(PreferencesConstants.DECIMAL_PLACES); /** * {@inheritDoc} */ @Override public void setInputDefinition(InputDefinition inputDefinition) { super.setInputDefinition(inputDefinition); template = new SqlStatementData(); template.setPlatformIdent(inputDefinition.getIdDefinition().getPlatformId()); template.setId(-1); if (inputDefinition.hasInputDefinitionExtra(InputDefinitionExtrasMarkerFactory.SQL_STATEMENT_EXTRAS_MARKER)) { SqlStatementInputDefinitionExtra inputDefinitionExtra = inputDefinition.getInputDefinitionExtra(InputDefinitionExtrasMarkerFactory.SQL_STATEMENT_EXTRAS_MARKER); template.setSql(inputDefinitionExtra.getSql()); } dataAccessService = inputDefinition.getRepositoryDefinition().getSqlDataAccessService(); cachedDataService = inputDefinition.getRepositoryDefinition().getCachedDataService(); } /** * {@inheritDoc} */ @Override public void createColumns(TreeViewer treeViewer) { for (Column column : Column.values()) { TreeViewerColumn viewerColumn = new TreeViewerColumn(treeViewer, SWT.NONE); viewerColumn.getColumn().setMoveable(true); viewerColumn.getColumn().setResizable(true); viewerColumn.getColumn().setText(column.name); viewerColumn.getColumn().setWidth(column.width); if (null != column.image) { viewerColumn.getColumn().setImage(column.image); } mapTreeViewerColumn(column, viewerColumn); } } /** * {@inheritDoc} */ @Override public Object getTreeInput() { return inputMap.keySet(); } /** * {@inheritDoc} */ @Override public IContentProvider getContentProvider() { return new SqlContentProvider(); } /** * {@inheritDoc} */ @Override public IBaseLabelProvider getLabelProvider() { return new SqlLabelProvider(); } /** * {@inheritDoc} */ @Override public ViewerComparator getComparator() { TreeViewerComparator<SqlStatementData> sqlViewerComparator = new DatabaseSqlTreeComparator(); for (Column column : Column.values()) { if (null != column.dataComparator) { ResultComparator<SqlStatementData> resultComparator = new ResultComparator<>(column.dataComparator, cachedDataService); sqlViewerComparator.addColumn(getMappedTreeViewerColumn(column).getColumn(), resultComparator); } } return sqlViewerComparator; } /** * {@inheritDoc} */ @Override public Set<PreferenceId> getPreferenceIds() { Set<PreferenceId> preferences = EnumSet.noneOf(PreferenceId.class); if (getInputDefinition().getRepositoryDefinition() instanceof CmrRepositoryDefinition) { preferences.add(PreferenceId.CLEAR_BUFFER); preferences.add(PreferenceId.LIVEMODE); } preferences.add(PreferenceId.UPDATE); preferences.add(PreferenceId.TIME_RESOLUTION); preferences.add(PreferenceId.TIMELINE); return preferences; } /** * {@inheritDoc} */ @Override public void preferenceEventFired(PreferenceEvent preferenceEvent) { switch (preferenceEvent.getPreferenceId()) { case TIMELINE: if (preferenceEvent.getPreferenceMap().containsKey(PreferenceId.TimeLine.FROM_DATE_ID)) { fromDate = (Date) preferenceEvent.getPreferenceMap().get(PreferenceId.TimeLine.FROM_DATE_ID); } if (preferenceEvent.getPreferenceMap().containsKey(PreferenceId.TimeLine.TO_DATE_ID)) { toDate = (Date) preferenceEvent.getPreferenceMap().get(PreferenceId.TimeLine.TO_DATE_ID); } break; case LIVEMODE: if (preferenceEvent.getPreferenceMap().containsKey(PreferenceId.LiveMode.BUTTON_LIVE_ID)) { autoUpdate = (Boolean) preferenceEvent.getPreferenceMap().get(PreferenceId.LiveMode.BUTTON_LIVE_ID); } break; case TIME_RESOLUTION: if (preferenceEvent.getPreferenceMap().containsKey(PreferenceId.TimeResolution.TIME_DECIMAL_PLACES_ID)) { timeDecimalPlaces = (Integer) preferenceEvent.getPreferenceMap().get(PreferenceId.TimeResolution.TIME_DECIMAL_PLACES_ID); } break; default: break; } } /** * {@inheritDoc} */ @Override public boolean canOpenInput(List<? extends Object> data) { if (data != null) { for (Object object : data) { if (!(object instanceof SqlStatementData)) { return false; } } } return true; } /** * {@inheritDoc} */ @Override public void doRefresh(IProgressMonitor monitor, final IRootEditor rootEditor) { monitor.beginTask("Getting SQL information", IProgressMonitor.UNKNOWN); List<SqlStatementData> sqlStatementList; if (autoUpdate) { sqlStatementList = dataAccessService.getAggregatedSqlStatements(template); } else { sqlStatementList = dataAccessService.getAggregatedSqlStatements(template, fromDate, toDate); } inputMap.clear(); if (CollectionUtils.isNotEmpty(sqlStatementList)) { inputMap.putAll(createInputMap(sqlStatementList)); } Display.getDefault().asyncExec(new Runnable() { @Override public void run() { if (null != rootEditor) { rootEditor.setDataInput(Collections.<DefaultData> emptyList()); } } }); monitor.done(); } /** * Create input map from list of {@link SqlStatementData}s. * * @param sqlStatementDatas * {@link SqlStatementData}s * @return Input map */ private Map<DatabaseInfoHelper, List<SqlStatementData>> createInputMap(List<SqlStatementData> sqlStatementDatas) { Map<DatabaseInfoHelper, List<SqlStatementData>> map = new HashMap<>(); for (SqlStatementData sqlStatementData : sqlStatementDatas) { DatabaseInfoHelper helper = new DatabaseInfoHelper(sqlStatementData); List<SqlStatementData> list = map.get(helper); if (null == list) { list = new ArrayList<>(); map.put(helper, list); } list.add(sqlStatementData); } return map; } /** * The sql label provider used by this view. * * @author Patrice Bouillet * */ private final class SqlLabelProvider extends StyledCellIndexLabelProvider { /** * Creates the styled text. * * @param element * The element to create the styled text for. * @param index * The index in the column. * @return The created styled string. */ @Override public StyledString getStyledText(Object element, int index) { Column enumId = Column.fromOrd(index); if (element instanceof SqlStatementData) { return getSqlStyledTextForColumn((SqlStatementData) element, enumId); } else if (element instanceof DatabaseInfoHelper) { return getDatabaseStyledTextForColumn((DatabaseInfoHelper) element, enumId); } return EMPTY_STYLED_STRING; } /** * {@inheritDoc} */ @Override public String getToolTipText(Object element, int index) { if (element instanceof DatabaseInfoHelper) { DatabaseInfoHelper helper = (DatabaseInfoHelper) element; return helper.getLongText(); } return super.getToolTipText(element, index); } } /** * The sql content provider used by this view. * * @author Patrice Bouillet * */ private final class SqlContentProvider extends ArrayContentProvider implements ITreeContentProvider { /** * {@inheritDoc} */ @Override public Object[] getChildren(Object parentElement) { if (parentElement instanceof DatabaseInfoHelper) { return inputMap.get(parentElement).toArray(); } return new Object[0]; } /** * {@inheritDoc} */ @Override public Object getParent(Object element) { if (element instanceof SqlStatementData) { return new DatabaseInfoHelper((SqlStatementData) element); } return null; } /** * {@inheritDoc} */ @Override public boolean hasChildren(Object element) { return element instanceof DatabaseInfoHelper; } } /** * {@inheritDoc} */ @Override public void doubleClick(DoubleClickEvent event) { final StructuredSelection selection = (StructuredSelection) event.getSelection(); if (!selection.isEmpty() && (selection.getFirstElement() instanceof SqlStatementData)) { try { PlatformUI.getWorkbench().getProgressService().busyCursorWhile(new IRunnableWithProgress() { @Override public void run(final IProgressMonitor monitor) { monitor.beginTask("Retrieving Parameter Aggregated SQLs", IProgressMonitor.UNKNOWN); SqlStatementData data = (SqlStatementData) selection.getFirstElement(); List<SqlStatementData> dataList = Collections.emptyList(); boolean hasNoParameters = !data.isPreparedStatement(); if (data.isPreparedStatement()) { dataList = dataAccessService.getParameterAggregatedSqlStatements(data, fromDate, toDate); // if we have only one statement and it has no parameters, we won't load // the bottom part with empty parameters if ((dataList.size() == 1) && CollectionUtils.isEmpty(dataList.get(0).getParameterValues())) { hasNoParameters = true; } } if (hasNoParameters) { final SqlHolderHelper inputForParametersTable = new SqlHolderHelper(Collections.<SqlStatementData> emptyList(), true); final SqlHolderHelper inputForTextOnly = new SqlHolderHelper(Collections.singletonList(data), false); Display.getDefault().asyncExec(new Runnable() { @Override public void run() { IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); IWorkbenchPage page = window.getActivePage(); IRootEditor rootEditor = (IRootEditor) page.getActiveEditor(); if (null != rootEditor) { rootEditor.setDataInput(Collections.singletonList(inputForParametersTable)); rootEditor.setDataInput(Collections.singletonList(inputForTextOnly)); } } }); } else { final SqlHolderHelper inputForParametersTable = new SqlHolderHelper(dataList, true); Display.getDefault().asyncExec(new Runnable() { @Override public void run() { IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); IWorkbenchPage page = window.getActivePage(); IRootEditor rootEditor = (IRootEditor) page.getActiveEditor(); if (null != rootEditor) { rootEditor.setDataInput(Collections.singletonList(inputForParametersTable)); } } }); } monitor.done(); } }); } catch (InvocationTargetException e) { MessageDialog.openError(Display.getDefault().getActiveShell().getShell(), "Error", e.getCause().toString()); } catch (InterruptedException e) { MessageDialog.openInformation(Display.getDefault().getActiveShell().getShell(), "Cancelled", e.getCause().toString()); } } } /** * {@inheritDoc} */ @Override public int getExpandLevel() { return AbstractTreeViewer.ALL_LEVELS; } /** * Returns the styled text for a specific column. * * @param data * The data object to extract the information from. * @param enumId * The enumeration ID. * @return The styled string containing the information from the data object. */ private StyledString getSqlStyledTextForColumn(SqlStatementData data, Column enumId) { switch (enumId) { case STATEMENT: String sql = data.getSql().replaceAll("[\r\n]+", " "); return new StyledString(sql); case INVOCATION_AFFILLIATION: int percentage = (int) (data.getInvocationAffiliationPercentage() * 100); int invocations = 0; if (null != data.getInvocationParentsIdSet()) { invocations = data.getInvocationParentsIdSet().size(); } return TextFormatter.getInvocationAffilliationPercentageString(percentage, invocations); case COUNT: return new StyledString(Long.toString(data.getCount())); case AVERAGE: return new StyledString(NumberFormatter.formatDouble(data.getAverage(), timeDecimalPlaces)); case MIN: return new StyledString(NumberFormatter.formatDouble(data.getMin(), timeDecimalPlaces)); case MAX: return new StyledString(NumberFormatter.formatDouble(data.getMax(), timeDecimalPlaces)); case DURATION: return new StyledString(NumberFormatter.formatDouble(data.getDuration(), timeDecimalPlaces)); case PREPARED: if (data.isPreparedStatement()) { return new StyledString("true"); } else { return new StyledString("false"); } default: return EMPTY_STYLED_STRING; } } /** * Returns the styled text for a specific column. * * @param data * {@link DatabaseInfoHelper}. * @param enumId * The enumeration ID. * @return The styled string containing the information from the data object. */ private StyledString getDatabaseStyledTextForColumn(DatabaseInfoHelper data, Column enumId) { switch (enumId) { case DATABASE_URL: return new StyledString(data.getDatabaseUrl()); default: return EMPTY_STYLED_STRING; } } /** * {@inheritDoc} */ @Override public String getReadableString(Object object) { if (object instanceof SqlStatementData) { SqlStatementData data = (SqlStatementData) object; StringBuilder sb = new StringBuilder(); for (Column column : Column.values()) { sb.append(getSqlStyledTextForColumn(data, column).toString()); sb.append('\t'); } return sb.toString(); } else if (object instanceof DatabaseInfoHelper) { DatabaseInfoHelper data = (DatabaseInfoHelper) object; StringBuilder sb = new StringBuilder(); for (Column column : Column.values()) { sb.append(getDatabaseStyledTextForColumn(data, column).toString()); sb.append('\t'); } return sb.toString(); } throw new RuntimeException("Could not create the human readable string!"); } /** * {@inheritDoc} */ @Override public List<String> getColumnValues(Object object) { if (object instanceof SqlStatementData) { SqlStatementData data = (SqlStatementData) object; List<String> values = new ArrayList<>(); for (Column column : Column.values()) { values.add(getSqlStyledTextForColumn(data, column).toString()); } return values; } else if (object instanceof DatabaseInfoHelper) { DatabaseInfoHelper data = (DatabaseInfoHelper) object; List<String> values = new ArrayList<>(); for (Column column : Column.values()) { values.add(getDatabaseStyledTextForColumn(data, column).toString()); } return values; } throw new RuntimeException("Could not create the column values!"); } /** * {@inheritDoc} */ @Override public Object[] getObjectsToSearch(Object treeInput) { List<SqlStatementData> sqlStatementDatas = new ArrayList<>(); for (List<SqlStatementData> datas : inputMap.values()) { sqlStatementDatas.addAll(datas); } return sqlStatementDatas.toArray(); } /** * {@inheritDoc} */ @Override public void dispose() { inputMap.clear(); } }