/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 ro.nextreports.designer.querybuilder; import info.clearthought.layout.TableLayout; import info.clearthought.layout.TableLayoutConstants; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Graphics; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Rectangle; import java.awt.Shape; import java.awt.event.ActionEvent; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.KeyEvent; import java.sql.Connection; import java.sql.Timestamp; import java.text.DateFormat; import java.text.DecimalFormat; import java.text.NumberFormat; import java.util.Date; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.ActionMap; import javax.swing.JCheckBox; import javax.swing.JEditorPane; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTextField; import javax.swing.JToolBar; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.text.BadLocationException; import javax.swing.text.Document; import javax.swing.text.Element; import javax.swing.text.JTextComponent; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jdesktop.swingx.JXTable; import org.jdesktop.swingx.decorator.ColorHighlighter; import org.jdesktop.swingx.decorator.HighlightPredicate; import org.jdesktop.swingx.decorator.Highlighter; import org.jdesktop.swingx.decorator.HighlighterFactory; import ro.nextreports.designer.Globals; import ro.nextreports.designer.datasource.DataSource; import ro.nextreports.designer.datasource.DefaultDataSourceManager; import ro.nextreports.designer.ui.IntegerTextField; import ro.nextreports.designer.ui.JLine; import ro.nextreports.designer.ui.TextHighlighter; import ro.nextreports.designer.ui.sqleditor.BaseEditorKit; import ro.nextreports.designer.ui.sqleditor.Editor; import ro.nextreports.designer.ui.table.TableRowHeader; import ro.nextreports.designer.util.ColorUtil; import ro.nextreports.designer.util.CopyTableMouseAdapter; import ro.nextreports.designer.util.I18NSupport; import ro.nextreports.designer.util.ImageUtil; import ro.nextreports.designer.util.MessageUtil; import ro.nextreports.designer.util.NextReportsUtil; import ro.nextreports.designer.util.ShortcutsUtil; import ro.nextreports.designer.util.Show; import ro.nextreports.designer.util.SwingUtil; import ro.nextreports.designer.util.TableUtil; import ro.nextreports.designer.util.UIActivator; import ro.nextreports.engine.EngineProperties; import ro.nextreports.engine.Report; import ro.nextreports.engine.exporter.util.ParametersBean; import ro.nextreports.engine.querybuilder.sql.Column; import ro.nextreports.engine.querybuilder.sql.dialect.CSVDialect; import ro.nextreports.engine.queryexec.QueryExecutor; import ro.nextreports.engine.queryexec.QueryResult; import ro.nextreports.engine.util.DialectUtil; import ro.nextreports.engine.util.ParameterNotFoundException; import ro.nextreports.engine.util.QueryUtil; import com.jgoodies.looks.HeaderStyle; import com.jgoodies.looks.Options; /** * @author Decebal Suiu */ public class SQLViewPanel extends JPanel { // default query retrieves all data from authors table public static final String DEFAULT_QUERY = "<<SELECT>>"; private static final Log LOG = LogFactory.getLog(SQLViewPanel.class); private JXTable resultTable; private ResultSetTableModel tableModel; private JTextComponent queryArea; private JCheckBox maxRowsCheckBox; private SQLStatusPanel statusPanel; private Thread executorThread; private Editor sqlEditor; private javax.swing.text.Highlighter.HighlightPainter errorPainter; private Object errorTag = null; private Action runAction; private static String sql; private boolean stop = false; public SQLViewPanel() { super(); initUI(); } private void initUI() { sqlEditor = new Editor() { public void afterCaretMove() { removeHighlightErrorLine(); } }; this.queryArea = sqlEditor.getEditorPanel().getEditorPane(); queryArea.setText(DEFAULT_QUERY); errorPainter = new javax.swing.text.Highlighter.HighlightPainter() { @Override public void paint(Graphics g, int p0, int p1, Shape bounds, JTextComponent c) { try { Rectangle r = c.modelToView(c.getCaretPosition()); g.setColor(Color.RED.brighter().brighter()); g.fillRect(0, r.y, c.getWidth(), r.height); } catch (BadLocationException e) { // ignore } } }; ActionMap actionMap = sqlEditor.getEditorPanel().getEditorPane().getActionMap(); // create the toolbar JToolBar toolBar = new JToolBar(); toolBar.putClientProperty("JToolBar.isRollover", Boolean.TRUE); // hide buttons borders toolBar.putClientProperty(Options.HEADER_STYLE_KEY, HeaderStyle.BOTH); toolBar.setBorderPainted(false); // add cut action Action cutAction = actionMap.get(BaseEditorKit.cutAction); cutAction.putValue(Action.SMALL_ICON, ImageUtil.getImageIcon("cut")); cutAction.putValue(Action.SHORT_DESCRIPTION, I18NSupport.getString("sqlviewpanel.cut")); toolBar.add(cutAction); // add copy action Action copyAction = actionMap.get(BaseEditorKit.copyAction); copyAction.putValue(Action.SMALL_ICON, ImageUtil.getImageIcon("copy")); copyAction.putValue(Action.SHORT_DESCRIPTION, I18NSupport.getString("sqlviewpanel.copy")); toolBar.add(copyAction); // add paste action Action pasteAction = actionMap.get(BaseEditorKit.pasteAction); pasteAction.putValue(Action.SMALL_ICON, ImageUtil.getImageIcon("paste")); pasteAction.putValue(Action.SHORT_DESCRIPTION, I18NSupport.getString("sqlviewpanel.paste")); toolBar.add(pasteAction); // add separator SwingUtil.addCustomSeparator(toolBar); // add undo action Action undoAction = actionMap.get(BaseEditorKit.undoAction); undoAction.putValue(Action.SMALL_ICON, ImageUtil.getImageIcon("undo")); undoAction.putValue(Action.SHORT_DESCRIPTION, I18NSupport.getString("undo")); toolBar.add(undoAction); // add redo action Action redoAction = actionMap.get(BaseEditorKit.redoAction); redoAction.putValue(Action.SMALL_ICON, ImageUtil.getImageIcon("redo")); redoAction.putValue(Action.SHORT_DESCRIPTION, I18NSupport.getString("redo")); toolBar.add(redoAction); // add separator SwingUtil.addCustomSeparator(toolBar); // add find action Action findReplaceAction = actionMap.get(BaseEditorKit.findReplaceAction); findReplaceAction.putValue(Action.SMALL_ICON, ImageUtil.getImageIcon("find")); findReplaceAction.putValue(Action.SHORT_DESCRIPTION, I18NSupport.getString("sqleditor.findReplaceActionName")); toolBar.add(findReplaceAction); // add separator SwingUtil.addCustomSeparator(toolBar); // add run action runAction = new SQLRunAction(); runAction.putValue(Action.SMALL_ICON, ImageUtil.getImageIcon("run")); runAction.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(ShortcutsUtil.getShortcut("query.run.accelerator", "control 4"))); runAction.putValue(Action.SHORT_DESCRIPTION, I18NSupport.getString("run.query") + " (" + ShortcutsUtil.getShortcut("query.run.accelerator.display", "Ctrl 4") + ")"); // runAction is globally registered in QueryBuilderPanel ! toolBar.add(runAction); // ro.nextreports.designer.util.SwingUtil.registerButtonsForFocus(buttonsPanel); // create the table resultTable = new JXTable(); resultTable.setDefaultRenderer(Integer.class, new ToStringRenderer()); // to remove thousand separators resultTable.setDefaultRenderer(Long.class, new ToStringRenderer()); resultTable.setDefaultRenderer(Date.class, new DateRenderer()); resultTable.setDefaultRenderer(Double.class, new DoubleRenderer()); resultTable.addMouseListener(new CopyTableMouseAdapter(resultTable)); TableUtil.setRowHeader(resultTable); resultTable.setColumnControlVisible(true); // resultTable.getTableHeader().setReorderingAllowed(false); resultTable.setHorizontalScrollEnabled(true); // highlight table Highlighter alternateHighlighter = HighlighterFactory.createAlternateStriping(Color.WHITE, ColorUtil.PANEL_BACKROUND_COLOR); Highlighter nullHighlighter = new TextHighlighter(ResultSetTableModel.NULL_VALUE, Color.YELLOW.brighter()); Highlighter blobHighlighter = new TextHighlighter(ResultSetTableModel.BLOB_VALUE, Color.GRAY.brighter()); Highlighter clobHighlighter = new TextHighlighter(ResultSetTableModel.CLOB_VALUE, Color.GRAY.brighter()); resultTable.setHighlighters(alternateHighlighter, nullHighlighter, blobHighlighter, clobHighlighter); resultTable.setBackground(ColorUtil.PANEL_BACKROUND_COLOR); resultTable.setGridColor(Color.LIGHT_GRAY); resultTable.setRolloverEnabled(true); resultTable.addHighlighter(new ColorHighlighter(HighlightPredicate.ROLLOVER_ROW, null, Color.RED)); JSplitPane split = new JSplitPane(JSplitPane.VERTICAL_SPLIT); split.setResizeWeight(0.66); split.setOneTouchExpandable(true); JPanel topPanel = new JPanel(); topPanel.setLayout(new GridBagLayout()); topPanel.add(toolBar, new GridBagConstraints(0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0)); topPanel.add(sqlEditor, new GridBagConstraints(0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0)); JPanel bottomPanel = new JPanel(); bottomPanel.setLayout(new GridBagLayout()); JScrollPane scrPanel = new JScrollPane(resultTable, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); statusPanel = new SQLStatusPanel(); bottomPanel.add(scrPanel, new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0)); bottomPanel.add(statusPanel, new GridBagConstraints(0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0)); split.setTopComponent(topPanel); split.setBottomComponent(bottomPanel); split.setDividerLocation(400); setLayout(new BorderLayout()); this.add(split, BorderLayout.CENTER); } public void setQueryString(String sqlString) { sql = sqlString; queryArea.setText(sqlString); } public String getQueryString() { String s = queryArea.getText(); if ("".equals(s)) { if (sql == null) { return ""; } else { return sql; } } else { return s; } } public boolean emptyQueryString() { String s = getQueryString(); return ("".equals(s) || DEFAULT_QUERY.equals(s)); } public void clear() { try { sql = ""; queryArea.setText(""); // tableModel = new ScrollingResultSetTableModel(null); tableModel = new CachingResultSetTableModel(null); resultTable.setModel(tableModel); statusPanel.clear(); } catch (Exception e) { e.printStackTrace(); JOptionPane.showMessageDialog(Globals.getMainFrame(), e, I18NSupport.getString("error"), JOptionPane.ERROR_MESSAGE); } } protected String getSql(Report report) { String sql; if (report == null) { sql = queryArea.getText(); } else { if (report.getSql() != null) { sql = report.getSql(); } else { try { for (Column col : report.getQuery().getColumns()) { col.getTable().setDialect(DialectUtil.getDialect(Globals.getConnection())); } } catch (Exception e) { e.printStackTrace(); LOG.error(e.getMessage(), e); } sql = report.getQuery().toString(); } } if (sql.equals("") || sql.equals(DEFAULT_QUERY)) { return null; } return sql; } protected ParametersBean selectParameters(Report report) { return selectParameters(report, null); } protected ParametersBean selectParameters(Report report, DataSource runDS) { String sql = getSql(report); return NextReportsUtil.selectParameters(sql, runDS); } protected QueryResult runQuery(ParametersBean pBean, boolean useMaxRows) throws Exception { return runQuery(Globals.getConnection(), pBean, useMaxRows); } protected QueryResult runQuery(Connection con, ParametersBean pBean, boolean useMaxRows) throws Exception { DataSource runDS = DefaultDataSourceManager.getInstance().getConnectedDataSource(); boolean isCsv = runDS.getDriver().equals(CSVDialect.DRIVER_CLASS); QueryExecutor executor = new QueryExecutor(pBean.getQuery(), pBean.getParams(), pBean.getParamValues(), con, true, true, isCsv); executor.setTimeout(Globals.getQueryTimeout()); if (useMaxRows) { executor.setMaxRows(maxRowsCheckBox.isSelected() ? statusPanel.getMaxRows() : 0); } else { executor.setMaxRows(Globals.getReportLayoutPanel().getRecords()); } return executor.execute(); } class SQLRunAction extends AbstractAction { public void actionPerformed(ActionEvent ev) { executorThread = new Thread(new Runnable() { public void run() { removeHighlightErrorLine(); if (MessageUtil.showReconnect()) { return; } final UIActivator activator = new UIActivator(Globals.getMainFrame(), I18NSupport.getString("running.query")); try { runAction.setEnabled(false); if (queryArea.getText().equals("")) { return; } String sql = getSql(null); try { ParameterManager.getInstance().parametersAreDefined(sql); } catch (ParameterNotFoundException ex) { Show.error(I18NSupport.getString("parameter.undefined", ex.getParamName())); return; } if (QueryUtil.restrictQueryExecution(sql)) { Show.error(I18NSupport.getString("export.action.execute")); return; } if (QueryUtil.isProcedureCall(sql)) { if (!QueryUtil.isValidProcedureCall(sql, DialectUtil.getDialect(Globals.getConnection()))) { Show.error(I18NSupport.getString("export.action.execute.procedure")); return; } // String param = ParameterManager.getInstance().parametersAreForStoredProcedure(); // if (param != null) { // Show.error(I18NSupport.getString("parameter.procedure.undefined", param)); // return; // } } ParametersBean pBean = selectParameters(null); if (pBean == null) { return; } activator.start(new SQLStopAction()); final QueryResult result = runQuery(pBean, true); if (result == null) { if (activator != null) { SwingUtilities.invokeLater(new Runnable() { public void run() { activator.stop(); } }); } return; } // TODO ?? // first reset model // tableModel = new ScrollingResultSetTableModel(null); tableModel = new CachingResultSetTableModel(null, executorThread.isInterrupted()); resultTable.setModel(tableModel); //tableModel = new ScrollingResultSetTableModel(result); tableModel = new CachingResultSetTableModel(result, executorThread.isInterrupted()); ((CachingResultSetTableModel)tableModel).init(); resultTable.setModel(tableModel); //resultTable.packAll(); statusPanel.setExecuteTime(result.getExecuteTime()); statusPanel.setRows(result.getRowCount()); TableRowHeader trh = TableUtil.setRowHeader(resultTable); trh.setBackground(ColorUtil.PANEL_BACKROUND_COLOR); if (result.getRowCount() == 0) { Show.info(I18NSupport.getString("run.query.nodata")); } } catch (InterruptedException e) { Show.dispose(); // close a possible previous dialog message Show.info(Globals.getMainFrame(), I18NSupport.getString("query.cancelled")); } catch (Exception e) { highlightErrorLine(e); e.printStackTrace(); Show.error(Globals.getMainFrame(), I18NSupport.getString("error"), e); } catch (Throwable t) { t.printStackTrace(); LOG.error(t.getMessage(), t); } finally { stop = false; runAction.setEnabled(true); if (activator != null) { SwingUtilities.invokeLater(new Runnable() { public void run() { activator.stop(); } }); } } } }, "NEXT : " + getClass().getSimpleName()); executorThread.setPriority(EngineProperties.getRunPriority()); executorThread.start(); } } private void highlightErrorLine(Exception e) { removeHighlightErrorLine(); // determine error line number, if any String er = e.getMessage(); // error may contain line number info like " line 12" or " line 12, column 3 " ... String regex = ".*\\sline\\s(\\d+)([^\\d].*)*"; Pattern p = Pattern.compile(regex); Matcher m = p.matcher(er); // first found is line number if (m.find()) { String lineNo = m.group(1); LOG.info("Error on line number : " + lineNo); Document ed = sqlEditor.getEditorPanel().getEditorPane().getDocument(); Element elem = ed.getDefaultRootElement().getElement(Integer.parseInt(lineNo)-1); sqlEditor.getEditorPanel().getEditorPane().setCaretPosition(elem.getStartOffset()); try { errorTag = sqlEditor.getEditorPanel().getEditorPane().getHighlighter().addHighlight(elem.getStartOffset(), elem.getEndOffset(), errorPainter); } catch (BadLocationException e1) { LOG.error("Could not highlight sql error line number", e1); } } } private void removeHighlightErrorLine() { if (errorTag != null) { sqlEditor.getEditorPanel().getEditorPane().getHighlighter().removeHighlight(errorTag); } } class SQLStopAction extends AbstractAction { public SQLStopAction() { super(); putValue(Action.NAME, I18NSupport.getString("stop.query.execution")); putValue(Action.SMALL_ICON, ImageUtil.getImageIcon("stop_execution")); putValue(Action.SHORT_DESCRIPTION, I18NSupport.getString("stop.query.execution")); putValue(Action.LONG_DESCRIPTION, I18NSupport.getString("stop.query.execution")); putValue(Action.MNEMONIC_KEY, new Integer('S')); putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_S, KeyEvent.CTRL_DOWN_MASK)); } public void actionPerformed(ActionEvent e) { if (stop) { Show.disposableInfo(I18NSupport.getString("stop.wait.second")); return; } else { Show.disposableInfo(I18NSupport.getString("stop.wait")); } if (executorThread != null) { stop = true; executorThread.interrupt(); ((CachingResultSetTableModel)tableModel).setStop(true); } } } class SQLStatusPanel extends JPanel { private final DecimalFormat timeFormat = new DecimalFormat("####.### sec"); private JTextField maxRowsTextField; private JLabel timeLabel; private JLabel rowsLabel; public SQLStatusPanel() { super(); initUI(); } public void setExecuteTime(long time) { timeLabel.setText(timeFormat.format((double) time / 1000)); } public void setRows(long rows) { rowsLabel.setText(rows + " " + I18NSupport.getString("rows")); } public int getMaxRows() { int rows; try { rows = Integer.parseInt(maxRowsTextField.getText()); } catch (NumberFormatException nfe) { //all rows rows = 0; } return rows; } private void initUI() { double[] columns = { TableLayoutConstants.FILL, TableLayoutConstants.PREFERRED, TableLayoutConstants.PREFERRED, TableLayoutConstants.PREFERRED }; double[] rows = { TableLayoutConstants.PREFERRED, }; TableLayout layout = new TableLayout(columns, rows); layout.setHGap(6); this.setLayout(layout); createMaxRowsPanel(); add(createMaxRowsPanel(), "0, 0"); add(timeLabel = new JLabel("0 " + I18NSupport.getString("seconds")), "1, 0"); add(new JLine(), "2, 0"); add(rowsLabel = new JLabel("0 " + I18NSupport.getString("rows")), "3, 0"); } private JPanel createMaxRowsPanel() { JPanel maxRowsPanel = new JPanel(); double[] columns = { TableLayoutConstants.PREFERRED, 3, TableLayoutConstants.PREFERRED }; double[] rows = { TableLayoutConstants.PREFERRED, }; TableLayout layout = new TableLayout(columns, rows); maxRowsPanel.setLayout(layout); maxRowsCheckBox = new JCheckBox(I18NSupport.getString("max.rows")); maxRowsCheckBox.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { if (e.getStateChange() == ItemEvent.SELECTED) { maxRowsTextField.setEditable(true); } else { maxRowsTextField.setEditable(false); } } }); maxRowsPanel.add(maxRowsCheckBox, "0, 0"); maxRowsTextField = new IntegerTextField(); maxRowsTextField.setText("500"); maxRowsTextField.setColumns(7); maxRowsPanel.add(maxRowsTextField, "2, 0"); // enable max rows feature enableMaxCheck(); return maxRowsPanel; } public void clear() { timeLabel.setText("0 " + I18NSupport.getString("seconds")); rowsLabel.setText("0 " + I18NSupport.getString("rows")); } public void enableMaxCheck() { boolean checked = Globals.isMaxChecked(); maxRowsCheckBox.setSelected(checked); if (checked) { maxRowsTextField.setEditable(true); } else { maxRowsTextField.setEditable(false); } } } public void enableMaxCheck() { statusPanel.enableMaxCheck(); } /** * Overwrite some renderers in JXTable because we need * to take care about ResultSetTableModel.NULL_VALUE value * (which is a String)! */ private class DateRenderer extends DefaultTableCellRenderer { DateFormat formatter; public DateRenderer() { super(); } public void setValue(Object value) { // Timestamp is also a Date, but we want to show also the time for it if (value instanceof Timestamp) { setText((value == null) ? "" : value.equals(ResultSetTableModel.NULL_VALUE) ? ResultSetTableModel.NULL_VALUE : value.toString()); } else { if (formatter == null) { formatter = DateFormat.getDateInstance(); } setText((value == null) ? "" : value.equals(ResultSetTableModel.NULL_VALUE) ? ResultSetTableModel.NULL_VALUE : formatter.format(value)); } } } private class DoubleRenderer extends DefaultTableCellRenderer { NumberFormat formatter; public DoubleRenderer() { super(); } public void setValue(Object value) { if (formatter == null) { formatter = NumberFormat.getInstance(); } setText((value == null) ? "" : value.equals(ResultSetTableModel.NULL_VALUE) ? ResultSetTableModel.NULL_VALUE : formatter.format(value)); } } private class ToStringRenderer extends DefaultTableCellRenderer { public ToStringRenderer() { super(); } public void setValue(Object value) { setText((value == null) ? "" : value.equals(ResultSetTableModel.NULL_VALUE) ? ResultSetTableModel.NULL_VALUE :value.toString()); } } public void doRun() { runAction.actionPerformed(null); } public JEditorPane getEditorPane() { return sqlEditor.getEditorPanel().getEditorPane(); } }