0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041: package org.netbeans.modules.db.sql.visualeditor.querybuilder;
0042:
0043: import java.awt.Component;
0044: import java.awt.Cursor;
0045: import java.awt.event.ActionEvent;
0046: import java.awt.event.KeyEvent;
0047: import java.awt.event.KeyListener;
0048: import java.awt.datatransfer.Clipboard;
0049: import java.awt.datatransfer.ClipboardOwner;
0050: import java.awt.datatransfer.Transferable;
0051:
0052: import javax.swing.JComponent;
0053: import javax.swing.ImageIcon;
0054: import javax.swing.JOptionPane;
0055: import javax.swing.SwingUtilities;
0056: import javax.swing.AbstractAction;
0057: import javax.swing.KeyStroke;
0058: import javax.swing.InputMap;
0059: import javax.swing.ActionMap;
0060: import javax.swing.JFrame;
0061: import javax.swing.RepaintManager;
0062: import javax.swing.text.DefaultEditorKit;
0063:
0064: import java.sql.ResultSet;
0065: import java.sql.SQLException;
0066: import java.sql.PreparedStatement;
0067: import java.sql.ParameterMetaData;
0068: import java.sql.Connection;
0069:
0070: import java.util.ArrayList;
0071: import java.util.List;
0072:
0073: import java.util.logging.Level;
0074: import org.netbeans.api.db.explorer.ConnectionManager;
0075: import org.netbeans.modules.db.sql.visualeditor.QueryEditorUILogger;
0076:
0077: import org.openide.NotifyDescriptor;
0078: import org.openide.DialogDisplayer;
0079: import org.openide.actions.DeleteAction;
0080: import org.openide.nodes.Node;
0081: import org.openide.util.HelpCtx;
0082: import org.openide.util.NbBundle;
0083: import org.openide.util.actions.ActionPerformer;
0084: import org.openide.util.actions.SystemAction;
0085: import org.openide.windows.TopComponent;
0086: import org.openide.windows.WindowManager;
0087:
0088: import org.netbeans.modules.db.sql.visualeditor.querymodel.ColumnProvider;
0089: import org.netbeans.modules.db.sql.visualeditor.querymodel.Column;
0090: import org.netbeans.modules.db.sql.visualeditor.querymodel.JoinTable;
0091: import org.netbeans.modules.db.sql.visualeditor.querymodel.OrderBy;
0092: import org.netbeans.modules.db.sql.visualeditor.parser.ParseException;
0093: import org.netbeans.modules.db.sql.visualeditor.parser.TokenMgrError;
0094:
0095: import org.netbeans.modules.db.sql.visualeditor.api.VisualSQLEditorMetaData;
0096: import org.netbeans.modules.db.sql.visualeditor.api.VisualSQLEditor;
0097:
0098: import org.netbeans.modules.db.sql.visualeditor.Log;
0099:
0100: import org.netbeans.api.db.explorer.DatabaseConnection;
0101: import org.netbeans.api.db.sql.support.SQLIdentifiers;
0102: import org.openide.util.Exceptions;
0103:
0104: /**
0105: * The top-level class for the QueryBuilder.
0106: *
0107: * This is the class that gets invoked from outside, to create QueryBuilder
0108: * instances. It also handles communication between the QueryBuilder panes and the
0109: * data source (RowSet, Connection), including retrieving metadata, executing
0110: * queries, and retrieving result sets.
0111: *
0112: * @author Sanjay Dhamankar, Jim Davidson, then hacked apart, chewed up, by jfbrown, but still needs more!
0113: */
0114: public class QueryBuilder extends TopComponent implements
0115: ClipboardOwner, KeyListener, ColumnProvider {
0116:
0117: // Provide package access on these, for use by other classes in the Query Builder
0118:
0119: QueryBuilderPane _queryBuilderPane;
0120: QueryModel _queryModel;
0121: boolean _updateModel = true;
0122: boolean _graphicsEnabled = true;
0123:
0124: // Used to delay updating text pane until all changes have been made
0125: boolean _updateText = true;
0126: SQLIdentifiers.Quoter quoter;
0127:
0128: // Private variables
0129: private String lastQuery;
0130:
0131: // the boolean below is used to determine if we need to store the query in the
0132: // backing file. The first time that generateText is called, we avoid saving
0133: // the query in the backing file as this unnecessarily sets the save buttons ON.
0134: private boolean firstTimeGenerateText = true;
0135:
0136: private String _parseErrorMessage = null;
0137: private boolean DEBUG = false;
0138: private DatabaseConnection dbconn;
0139: private String statement;
0140: private QueryBuilderMetaData qbMetaData;
0141: // private VisualSQLEditorMetaData metaDataCache = null ;
0142: private VisualSQLEditor vse;
0143: private SQLException lastException = null;
0144:
0145: /**
0146: * Static factory method, added for access from RowSet
0147: * @Return a QueryBuilder instance, either new or retrieved from the Map
0148: */
0149: public static Component open(DatabaseConnection dbconn,
0150: String statement, VisualSQLEditorMetaData metadata,
0151: VisualSQLEditor vse) {
0152: Log.getLogger().entering("QueryBuilder", "open"); // NOI18N
0153: QueryEditorUILogger.logEditorOpened();
0154:
0155: showBusyCursor(true);
0156:
0157: QueryBuilder qb;
0158: try {
0159: qb = new QueryBuilder(dbconn, statement, metadata, vse);
0160: } catch (SQLException sqle) {
0161: qb = null;
0162: // JDTODO: restore this dialog
0163: // // TODO: popup an error dialog.
0164: // ConnectionStatusPanel csp = new ConnectionStatusPanel() ;
0165: // csp.configureDisplay(sqlStatement.getConnectionInfo(), false,sqle.getLocalizedMessage(), "", 0, false ) ;
0166: // // csp.setGeneralInfo("") ;
0167: // csp.displayDialog( sqlStatement.getConnectionInfo() ) ;
0168: }
0169: final QueryBuilder queryBuilder = qb;
0170: SwingUtilities.invokeLater(new Runnable() {
0171: public void run() {
0172: if (queryBuilder != null) {
0173: queryBuilder.open();
0174: queryBuilder.requestActive();
0175: }
0176: showBusyCursor(false);
0177: }
0178: });
0179:
0180: queryBuilder.getTextAreaFocusInvokeLater();
0181: return queryBuilder;
0182: }
0183:
0184: /**
0185: * Private constructor, invoked only from the open() factory method
0186: */
0187: private QueryBuilder(DatabaseConnection dbconn, String statement,
0188: VisualSQLEditorMetaData metadata, VisualSQLEditor vse)
0189: throws SQLException {
0190: Log.getLogger().entering("QueryBuilder", "constructor");
0191: this .dbconn = dbconn;
0192: this .statement = statement;
0193: this .vse = vse;
0194:
0195: // Either pass in metadata, or have it created from db
0196: this .qbMetaData = (metadata == null) ? new QueryBuilderMetaData(
0197: dbconn, this )
0198: : new QueryBuilderMetaData(metadata, this );
0199:
0200: // Create a quoter to be used for delimiting identifiers
0201: this .quoter = SQLIdentifiers.createQuoter(getConnection()
0202: .getMetaData());
0203:
0204: // It would be nice to have a short title, but there isn't a convenient one
0205: String title = dbconn.getName();
0206:
0207: // Set the name to display
0208: setName(title);
0209: setDisplayName(title);
0210:
0211: setLayout(new java.awt.BorderLayout());
0212:
0213: ImageIcon imgIcon = new ImageIcon(
0214: getClass()
0215: .getResource(
0216: "/org/netbeans/modules/db/sql/visualeditor/resources/query-editor-tab.png")); // NOI18N
0217: if (imgIcon != null)
0218: setIcon(imgIcon.getImage());
0219:
0220: _queryBuilderPane = new QueryBuilderPane(this );
0221:
0222: // Add the pane to the end of the QueryBuilder container
0223: add(_queryBuilderPane);
0224:
0225: addKeyListener(this );
0226: }
0227:
0228: // used for syntax highlighting
0229: public boolean isSchemaName(String schemaName) {
0230: return qbMetaData.isSchemaName(schemaName);
0231: }
0232:
0233: public boolean isTableName(String tableName) {
0234: return qbMetaData.isTableName(tableName);
0235: }
0236:
0237: public boolean isColumnName(String columnName) {
0238: return qbMetaData.isColumnName(columnName);
0239: }
0240:
0241: /////////////////////////////////////////////////////////////////////////
0242: // Delete support
0243: /////////////////////////////////////////////////////////////////////////
0244:
0245: /** delete action performer */
0246: private final transient DeleteActionPerformer deleteActionPerformer = new DeleteActionPerformer();
0247:
0248: /** copy action performer */
0249: protected final transient CopyCutActionPerformer copyActionPerformer = new CopyCutActionPerformer(
0250: true);
0251:
0252: /** cut action performer */
0253: protected final transient CopyCutActionPerformer cutActionPerformer = new CopyCutActionPerformer(
0254: false);
0255:
0256: // Implements ClipboardOwner
0257: public void lostOwnership(Clipboard clipboard, Transferable contents) {
0258: }
0259:
0260: boolean isSelectionEmpty() {
0261: Node[] nodes = getActivatedNodes();
0262: if ((nodes != null) && (nodes.length != 0))
0263: return false;
0264: else
0265: return true;
0266: }
0267:
0268: private boolean isActivated() {
0269: return this == TopComponent.getRegistry().getActivated();
0270: }
0271:
0272: /*** do not save across IDE session ***/
0273: public int getPersistenceType() {
0274: return TopComponent.PERSISTENCE_NEVER;
0275: }
0276:
0277: public String preferredID() {
0278: return "QueryEditor"; //NOI18N
0279: }
0280:
0281: /** Called when this window is activated: make delete
0282: * sensitive based on whether or not anything is selected and whether
0283: * the clipboard contains something we can absorb. */
0284: public void activateActions() {
0285: if (isSelectionEmpty()) {
0286: disableDelete();
0287: } else {
0288: enableDelete();
0289: }
0290: // for now cut / copy is always disabled.
0291: disableCutCopy();
0292: }
0293:
0294: /** Called when the when the component is deactivated. We no longer
0295: * allow our paste types to be invoked so clear it - get rid of
0296: * the action performers as well. */
0297: public void deactivateActions() {
0298: if (deleteActionPerformer != null) {
0299: // delete.setActionPerformer(null);
0300: }
0301: }
0302:
0303: /** Called when the selection is non zero and the component is active:
0304: * enable cut, copy and delete */
0305: void enableDelete() {
0306: if (!isActivated()) {
0307: return;
0308: }
0309: deleteActionPerformer.setEnabled(true);
0310: }
0311:
0312: /** Called when the selection is removed: disable delete */
0313: void disableDelete() {
0314: if (!isActivated()) {
0315: return;
0316: }
0317: deleteActionPerformer.setEnabled(false);
0318: DeleteAction da = SystemAction.get(DeleteAction.class);
0319: da.setEnabled(false);
0320: }
0321:
0322: /** Called when the selection is removed: disable cut/copy */
0323: void disableCutCopy() {
0324: if (DEBUG)
0325: System.out.println(" disableCutCopy called. " + "\n"); // NOI18N
0326: // Fix 6265915 Copy/Cut menu items are always enabled
0327: cutActionPerformer.setEnabled(false);
0328: copyActionPerformer.setEnabled(false);
0329: }
0330:
0331: /** Class which performs delete action */
0332: class DeleteActionPerformer extends AbstractAction implements
0333: ActionPerformer {
0334:
0335: public void actionPerformed(ActionEvent e) {
0336: performAction(null);
0337: }
0338:
0339: // Perform delete action.
0340: public void performAction(SystemAction action) {
0341: // We run into deadlocks without this; !#$!@#!@ ModuleActions thread
0342: SwingUtilities.invokeLater(new Runnable() {
0343: public void run() {
0344: deleteSelection();
0345: }
0346: });
0347: }
0348: }
0349:
0350: /** Class which performs copy and cut actions */
0351: class CopyCutActionPerformer extends AbstractAction implements
0352: ActionPerformer {
0353: /** determine if adapter is used for copy or cut action. */
0354: boolean isCopy;
0355:
0356: /** Create new adapter */
0357: public CopyCutActionPerformer(boolean b) {
0358: isCopy = b;
0359: }
0360:
0361: public void actionPerformed(ActionEvent e) {
0362: performAction(null);
0363: }
0364:
0365: /** Perform copy or cut action. */
0366: public void performAction(SystemAction action) {
0367: // for now do nothing
0368: }
0369: }
0370:
0371: /** Remove the currently selected components */
0372: private void deleteSelection() {
0373: if (DEBUG)
0374: System.out.println(" deleteSelection called. " + "\n"); // NOI18N
0375: java.awt.KeyboardFocusManager kbfm = java.awt.KeyboardFocusManager
0376: .getCurrentKeyboardFocusManager();
0377: java.awt.Component c = kbfm.getFocusOwner();
0378: if (c != null) {
0379: java.awt.Container p = c.getParent();
0380: while (p != null) {
0381: if (p instanceof QueryBuilderGraphFrame) {
0382:
0383: Node[] nodes = getActivatedNodes();
0384: if (nodes == null || nodes.length == 0) {
0385: return;
0386: }
0387: for (int i = 0; i < nodes.length; i++) {
0388: if ((nodes[i] instanceof CondNode)) {
0389: _queryBuilderPane
0390: .getQueryBuilderGraphFrame()
0391: .removeNode((CondNode) nodes[i]);
0392: } else if ((nodes[i] instanceof JoinNode)) {
0393: _queryBuilderPane
0394: .getQueryBuilderGraphFrame()
0395: .removeNode((JoinNode) nodes[i]);
0396: } else if ((nodes[i] instanceof TableNode)) {
0397: _queryBuilderPane
0398: .getQueryBuilderGraphFrame()
0399: .removeNode((TableNode) nodes[i]);
0400: }
0401: }
0402: }
0403: p = p.getParent();
0404: }
0405: }
0406: }
0407:
0408: private void installActions(ActionMap map, InputMap keys) {
0409: /*
0410: map.put(DefaultEditorKit.copyAction, copyActionPerformer);
0411: map.put(DefaultEditorKit.cutAction, cutActionPerformer);
0412: // Paste still done the old way...
0413: //map.put(DefaultEditorKit.pasteAction, pasteActionPerformer);
0414: */
0415: map.put("delete", deleteActionPerformer); // or false
0416: map.put(DefaultEditorKit.copyAction, copyActionPerformer);
0417: map.put(DefaultEditorKit.cutAction, cutActionPerformer);
0418:
0419: /*
0420: // Popup menu from the keyboard
0421: map.put ("org.openide.actions.PopupAction",
0422: new AbstractAction() {
0423: public void actionPerformed(ActionEvent evt) {
0424: SwingUtilities.invokeLater(new Runnable() {
0425: public void run() {
0426: showKeyboardPopup();
0427: }
0428: });
0429: }
0430: });
0431:
0432: keys.put(KeyStroke.getKeyStroke("control C"), DefaultEditorKit.copyAction);
0433: keys.put(KeyStroke.getKeyStroke("control X"), DefaultEditorKit.cutAction);
0434: keys.put(KeyStroke.getKeyStroke("control V"), DefaultEditorKit.pasteAction);
0435: */
0436: keys.put(KeyStroke.getKeyStroke("DELETE"), "delete");
0437: }
0438:
0439: void getGraphFrameCanvasFocus() {
0440: _queryBuilderPane.getQueryBuilderGraphFrame().getCanvasFocus();
0441: }
0442:
0443: void getTextAreaFocusInvokeLater() {
0444: javax.swing.SwingUtilities.invokeLater(new Runnable() {
0445: public void run() {
0446: _queryBuilderPane.getQueryBuilderSqlTextArea()
0447: .requestFocus(true);
0448: _queryBuilderPane.getQueryBuilderSqlTextArea()
0449: .requestFocusInWindow();
0450: }
0451: });
0452: }
0453:
0454: /** ignore */
0455: public void keyTyped(KeyEvent e) {
0456: }
0457:
0458: /** ignore */
0459: public void keyReleased(KeyEvent e) {
0460: }
0461:
0462: /** Handle the key pressed event and change the focus if a particular
0463: * key combination is pressed. */
0464: public void keyPressed(KeyEvent e) {
0465: handleKeyPress(e);
0466: }
0467:
0468: public void handleKeyPress(KeyEvent e) {
0469: if (e.isAltDown()) {
0470: int code = e.getKeyCode();
0471: switch (code) {
0472: // diagram pane
0473: case KeyEvent.VK_1:
0474: if (DEBUG)
0475: System.out.println(" Alt + 1 pressed. "); // NOI18N
0476: // ToDo: Decide whether this needs to be duplicated in the GraphLib version
0477: // _queryBuilderPane.getQueryBuilderGraphFrame().getFocus ();
0478: getGraphFrameCanvasFocus();
0479: break;
0480: // grid pane
0481: case KeyEvent.VK_2:
0482: if (DEBUG)
0483: System.out.println(" Alt + 2 pressed. "); // NOI18N
0484: if (_queryBuilderPane.getQueryBuilderInputTable()
0485: .getRowCount() > 0) {
0486: _queryBuilderPane.getQueryBuilderInputTable()
0487: .setRowSelectionInterval(0, 0);
0488: _queryBuilderPane.getQueryBuilderInputTable()
0489: .requestFocus(true);
0490: }
0491: break;
0492: // SQL text pane
0493: case KeyEvent.VK_3:
0494: if (DEBUG)
0495: System.out.println(" Alt + 3 pressed. "); // NOI18N
0496: _queryBuilderPane.getQueryBuilderSqlTextArea()
0497: .requestFocus(true);
0498: break;
0499: // Result Pane
0500: case KeyEvent.VK_4:
0501: if (DEBUG)
0502: System.out.println(" Alt + 4 pressed. "); // NOI18N
0503: _queryBuilderPane.getQueryBuilderResultTable()
0504: .requestFocus(true);
0505: break;
0506: }
0507: }
0508: }
0509:
0510: String checkTableName(String tableName) throws SQLException {
0511: return qbMetaData.checkTableName(tableName);
0512: }
0513:
0514: String checkFullTableName(String fullTableName) throws SQLException {
0515: return qbMetaData.checkFullTableName(fullTableName);
0516: }
0517:
0518: String checkColumnName(String tableName, String columnName)
0519: throws SQLException {
0520: return qbMetaData.checkColumnName(tableName, columnName);
0521: }
0522:
0523: boolean checkColumnNameForTable(Column col, String tableName) {
0524: return qbMetaData.checkColumnNameForTable(col, tableName);
0525: }
0526:
0527: boolean checkTableColumnName(Column col) throws SQLException {
0528: return qbMetaData.checkTableColumnName(col);
0529: }
0530:
0531: /****
0532: * Check the database connection.
0533: * If no connection, ask to retry.
0534: * if user doesn't want to retry,
0535: * then disable the query editor
0536: */
0537: // public boolean checkDatabaseAndDisable(String query) {
0538: // if ( query == null )
0539: // query = _queryBuilderPane.getQueryBuilderSqlTextArea().getText() ;
0540: // if ( checkDatabaseConnection() == false ) {
0541: // Log.getLogger().finest("checkDatabaseConnection returns false ... \n " ); // NOI18N
0542: // // If we don't have a valid connection, disable all visual editing.
0543: // disableVisualEditing(query);
0544: // return false;
0545: // }
0546: // return true ;
0547: // }
0548: /**
0549: * Parse the query and regenerate all the panes
0550: * If parsing fails, raise an notification and do nothing else
0551: * If parsing succeeds, return true, false otherwise.
0552: * @param text query to parse
0553: * @param forceParse force a parse even if query hasn't changed
0554: */
0555: boolean populate(String query, boolean forceParse) {
0556:
0557: Log.getLogger().entering("QueryBuilder", "populate", query); // NOI18N
0558:
0559: if (!forceParse) {
0560: if (query.trim().equals(
0561: _queryBuilderPane.getQueryBuilderSqlTextArea()
0562: .getText().trim())) {
0563: // no change, just return.
0564: Log.getLogger().finest(
0565: " skipping populate(), no change"); //NOI18N
0566: return true;
0567: }
0568: }
0569:
0570: // Fix CR 6275870 Error when parsing invalid SQL
0571: if (query.trim().equals(lastQuery)) {
0572: // no change, just return.
0573: Log.getLogger().finest(" skipping populate(), no change"); //NOI18N
0574: return true;
0575: } else {
0576: lastQuery = new String(query.trim());
0577: }
0578:
0579: // if ( ! checkDatabaseAndDisable( query )) return false ;
0580:
0581: // First parse the query, and report any exception
0582: try {
0583: parseQuery(query);
0584:
0585: // if the parsing is successful and if the user modified query
0586: // by hand, then check for all the table names and column names.
0587: // if there is an error, give a message to the user and return false
0588: // else if there is only error in case and/or ommission of
0589: // tablename etc fix the query model with the correct values.
0590:
0591: if (!checkQuery())
0592: return false;
0593:
0594: _queryBuilderPane.getQueryBuilderGraphFrame()
0595: .setQBGFEnabled(true);
0596: _queryBuilderPane.setQueryBuilderInputTableEnabled(true);
0597: _queryBuilderPane.getQueryBuilderGraphFrame()
0598: .setTableColumnValidity(false);
0599: _queryBuilderPane.getQueryBuilderGraphFrame().setGroupBy(
0600: _queryModel.hasGroupBy());
0601: _graphicsEnabled = true;
0602: // Will be done later by generate()
0603: // setSqlText(query);
0604: } catch (ParseException pe) {
0605: Log.getLogger().severe(
0606: "Parse error: " + pe.getLocalizedMessage()); // NOI18N
0607: promptForContinuation(pe.getMessage(), query);
0608: return false;
0609: } catch (TokenMgrError tme) {
0610: Log.getLogger().severe(
0611: "Parse error: " + tme.getLocalizedMessage()); // NOI18N
0612: promptForContinuation(tme.getMessage(), query);
0613: return false;
0614:
0615: } catch (SQLException sqe) {
0616: lastException = sqe;
0617: Log.getLogger().severe(
0618: "Parse error: " + sqe.getLocalizedMessage()); // NOI18N
0619: promptForContinuation(sqe.getMessage(), query);
0620: return false;
0621: }
0622:
0623: _parseErrorMessage = null;
0624:
0625: // If parsing was successful...
0626:
0627: // ...generate the editor panes
0628: this .generate();
0629:
0630: // ...save the sql command.
0631: saveSqlCommand();
0632:
0633: _queryBuilderPane.getQueryBuilderSqlTextArea().requestFocus();
0634:
0635: return true;
0636: }
0637:
0638: /**
0639: * Ask the user whether to Retry&Continue or Cancel&Continue
0640: */
0641: private boolean promptForContinuation(String msg, String query) {
0642:
0643: // There could be an error or the typed SQL may not be SQL-92
0644: // compliant. Give the user an option to keep the query and test by
0645: // running it. If the user is satisfied then this could reflect in the
0646: // backing file. In that case the tables may not be displayed in the
0647: // graph properly. If the user thinks that there is a genuine error,
0648: // then the previous good query will be restored.
0649:
0650: Object[] options = {
0651: NbBundle.getMessage(QueryBuilder.class, "CONTINUE"), // "Continue"
0652: NbBundle.getMessage(QueryBuilder.class, "CANCEL") // "Cancel"
0653: };
0654: if (_queryBuilderPane.getQueryBuilderSqlTextArea()
0655: .queryChanged()) {
0656: int val = JOptionPane.showOptionDialog(this , (msg
0657: + "\n\n"
0658: + NbBundle.getMessage(QueryBuilder.class,
0659: "PARSE_ERROR_MESSAGE") + NbBundle
0660: .getMessage(QueryBuilder.class,
0661: "PARSE_ERROR_MESSAGE_PROMPT")), NbBundle
0662: .getMessage(QueryBuilder.class, "PARSE_ERROR"),
0663: JOptionPane.YES_NO_OPTION,
0664: JOptionPane.QUESTION_MESSAGE, null, options,
0665: options[0]);
0666: if (val == JOptionPane.NO_OPTION) { // Cancel - Revert to previous
0667: Log.getLogger().info("Query execution canceled"); // NOI18N
0668: _queryBuilderPane.getQueryBuilderSqlTextArea()
0669: .restoreLastGoodQuery();
0670: _queryBuilderPane.getQueryBuilderGraphFrame()
0671: .setQBGFEnabled(true);
0672: _queryBuilderPane
0673: .setQueryBuilderInputTableEnabled(true);
0674: _graphicsEnabled = true;
0675: _parseErrorMessage = null;
0676: } else { // Continue - Disable visual editing
0677: _parseErrorMessage = NbBundle.getMessage(
0678: QueryBuilder.class, "PARSE_ERROR_MESSAGE"); // NOI18N
0679: disableVisualEditing(query);
0680: }
0681: } else {
0682: // display the message in the graph area
0683: _parseErrorMessage = NbBundle.getMessage(
0684: QueryBuilder.class, "PARSE_ERROR_MESSAGE"); // NOI18N
0685: disableVisualEditing(query);
0686: }
0687: return false;
0688: }
0689:
0690: // Disable the graph and grid panes, leaving only the text pane.
0691: // Used when either a parse fails, or the database is down
0692: private void disableVisualEditing(String query) {
0693: _graphicsEnabled = false;
0694: _queryBuilderPane.clear();
0695: _queryBuilderPane.getQueryBuilderGraphFrame().setQBGFEnabled(
0696: false);
0697: _queryBuilderPane.setQueryBuilderInputTableEnabled(false);
0698:
0699: String command = getSqlCommand();
0700: if (query != null && query.trim().length() != 0) {
0701: setSqlText(query);
0702: setSqlCommand(query);
0703: } else {
0704: setSqlText(command);
0705: }
0706: }
0707:
0708: /***************************************************************************
0709: * Code for checking validity of queries
0710: **************************************************************************/
0711:
0712: /**
0713: * Check the various parts of the query for valid table/column names
0714: */
0715: private boolean checkQuery() throws SQLException {
0716:
0717: if ((getSqlText() != null)
0718: || (_queryBuilderPane.getQueryBuilderGraphFrame()
0719: .checkTableColumnValidity())) {
0720:
0721: // from
0722: if (!checkFrom())
0723: return false;
0724:
0725: // We were calling a function to replace the "*" with the column
0726: // names of the tables immediately after parsing the query. With the
0727: // introduction of routines to check the table and column names and
0728: // to resolve them properly, if the from clause contains the table
0729: // names not as they appear in database, we change them to match
0730: // with those in the database. e.g. trip becomes "TRAVEL.TRIP". This
0731: // is done in function checkFrom which gets called after parsing is
0732: // done. In the case of replaceStar being called immediately after
0733: // parsing, the column names and table names were resolved without
0734: // checking for their validity in the database. I have changed this
0735: // sequence as follows:
0736: // 1. First parse the query.
0737: // 2. Check for table names in the from clause. If the names
0738: // only differ in case or if they are missing schema name
0739: // then they are corrected in the datamodel.
0740: // 3. These corrected table names are used to resolve the column
0741: // names in SQLs like "select * from trip" when we call replaceStar()
0742: // function. This should fix the problem where "select * from TRIP"
0743: // was parsed properly but "select * from trip" used to give errors.
0744: // Bug Id : 4962093
0745:
0746: // we need to replace star after we validate all the table names
0747: // in the from list.
0748: _queryModel.replaceStar(this );
0749:
0750: // select
0751: if (!checkSelect())
0752: return false;
0753:
0754: // where
0755: if (!checkWhere())
0756: return false;
0757:
0758: // groupby
0759: if (!checkGroupBy())
0760: return false;
0761:
0762: // having
0763: if (!checkHaving())
0764: return false;
0765:
0766: // orderby
0767: if (!checkOrderBy())
0768: return false;
0769:
0770: } else {
0771: // we are not validating the fromList as the query has not been
0772: // changed by the user.
0773: _queryModel.replaceStar(this );
0774: }
0775: return true;
0776: }
0777:
0778: /**
0779: * Check the tables specified in the FROM clause (plus any columns
0780: * specified in join conditions), against the DB Schema
0781: */
0782: private boolean checkFrom() throws SQLException {
0783:
0784: if (DEBUG)
0785: System.out.println("checkFrom called... \n "); // NOI18N
0786:
0787: // we could reuse this to find the tablename if the user
0788: // only specifies "select 'column_name' from 'table_name'"
0789: List fromTables;
0790: // from
0791: if (_queryModel.getFrom() != null) {
0792:
0793: fromTables = _queryModel.getFrom().getTableList();
0794: for (int i = 0; i < fromTables.size(); i++) {
0795:
0796: String fromTableName = ((JoinTable) fromTables.get(i))
0797: .getFullTableName();
0798: String fromTableSpec = ((JoinTable) fromTables.get(i))
0799: .getTableSpec();
0800: String checkedFullTableName = checkFullTableName(fromTableName);
0801:
0802: if (DEBUG)
0803: System.out
0804: .println("checkFullTableName called, fromTableName: "
0805: + fromTableName
0806: + " returns: "
0807: + checkedFullTableName + " \n "); // NOI18N
0808: if (checkedFullTableName == null) {
0809: // table not found, give an error
0810: showTableColumnNameError(fromTableName);
0811: return false;
0812: } else if (!checkedFullTableName.equals(fromTableName)) {
0813: // table found but maybe in a wrong case, replace
0814: // it in the querymodel's from
0815: if (DEBUG)
0816: System.out.println(" fromTableName = "
0817: + fromTableName + // NOI18N
0818: " fromTableSpec = " + fromTableSpec + // NOI18N
0819: " \n"); // NOI18N
0820: _queryModel.getFrom().setTableSpec(fromTableSpec,
0821: checkedFullTableName);
0822: }
0823:
0824: // now check the columns in the condition if any.
0825: List fromColumns = new ArrayList();
0826: ((JoinTable) fromTables.get(i))
0827: .getReferencedColumns(fromColumns);
0828: for (int j = 0; j < fromColumns.size(); j++) {
0829: Column fromColumn = (Column) fromColumns.get(j);
0830: if (!checkTableColumnName(fromColumn)) {
0831: showTableColumnNameError(fromColumn
0832: .getColumnName());
0833: return false;
0834: }
0835: }
0836: }
0837: }
0838: return true;
0839: }
0840:
0841: /**
0842: * Check the tables specified in the SELECT clause, against the DB Schema
0843: */
0844: private boolean checkSelect() throws SQLException {
0845: if (DEBUG)
0846: System.out
0847: .println("checkSelect called. _queryModel.getSelect() = "
0848: + _queryModel.getSelect()); // NOI18N
0849: if (_queryModel.getSelect() != null) {
0850: ArrayList selectColumns = new ArrayList();
0851: _queryModel.getSelect().getReferencedColumns(selectColumns);
0852: if (!checkColumns(selectColumns))
0853: return false;
0854: }
0855: return true;
0856: }
0857:
0858: /**
0859: * Check the tables specified in the WHERE clause against the DB schema
0860: */
0861: private boolean checkWhere() throws SQLException {
0862: if (DEBUG)
0863: System.out.println("checkWhere called... "); // NOI18N
0864: if (_queryModel.getWhere() != null) {
0865: ArrayList whereColumns = new ArrayList();
0866: _queryModel.getWhere().getReferencedColumns(whereColumns);
0867: if (!checkColumns(whereColumns))
0868: return false;
0869: }
0870: return true;
0871: }
0872:
0873: /**
0874: * Check the tables specified in the GROUP BY clause against the DB schema
0875: */
0876: private boolean checkGroupBy() throws SQLException {
0877: if (DEBUG)
0878: System.out.println("checkGroupBy called... "); // NOI18N
0879: if (_queryModel.getGroupBy() != null) {
0880: ArrayList groupByColumns = new ArrayList();
0881: _queryModel.getGroupBy().getReferencedColumns(
0882: groupByColumns);
0883: if (!checkColumns(groupByColumns))
0884: return false;
0885: }
0886: return true;
0887: }
0888:
0889: /**
0890: * Check the tables specified in the HAVING clause against the DB schema
0891: */
0892: private boolean checkHaving() throws SQLException {
0893: if (DEBUG)
0894: System.out.println("checkHaving called... "); // NOI18N
0895: if (_queryModel.getHaving() != null) {
0896: ArrayList havingColumns = new ArrayList();
0897: _queryModel.getHaving().getReferencedColumns(havingColumns);
0898: if (!checkColumns(havingColumns))
0899: return false;
0900: }
0901: return true;
0902: }
0903:
0904: /**
0905: * Check the tables specified in the ORDER BY clause against the DB schema
0906: */
0907: private boolean checkOrderBy() throws SQLException {
0908: if (DEBUG)
0909: System.out.println("checkOrderBy called... "); // NOI18N
0910: OrderBy orderBy = _queryModel.getOrderBy();
0911: if (orderBy != null) {
0912: ArrayList orderByColumns = new ArrayList();
0913: for (int i = 0; i < orderBy.getSortSpecificationCount(); i++) {
0914: Column sortColumn = orderBy.getSortSpecification(i)
0915: .getColumn();
0916: orderByColumns.add(sortColumn);
0917: }
0918: if (!checkColumns(orderByColumns))
0919: return false;
0920: }
0921: return true;
0922: }
0923:
0924: // Check and correct any columns that may have wrong or missing table
0925: // specifications. If the column is not found in the database it displays
0926: // an error message and returns false. If there is just case mismatch
0927: // this function corrects the column name.
0928: // The column name could be :
0929: // case 1 : <schema_name>.<table_name>.<column_name>
0930: // case 2 : <table_name>.<column_name>
0931: // case 3 : <alias_table_name>.<column_name>
0932: // case 4 : <column_name>
0933: //
0934: private boolean checkColumns(ArrayList columns) throws SQLException {
0935: Log.getLogger().entering("QueryBuilder", "checkColumns"); // NOI18N
0936: for (int i = 0; i < columns.size(); i++) {
0937: Column column = (Column) columns.get(i);
0938: String columnTableSpec = column.getTableSpec();
0939: String columnFullTableName = column.getFullTableName();
0940:
0941: // If the user has specified a column without tablename resolve it
0942: // from the from_table_list.
0943: // Reversed the first test, switched || to &&.
0944: if ((columnFullTableName == null)
0945: && (_queryModel.getFrom() != null)) {
0946: // Check every table in the From list, to see if any have
0947: // this column
0948: List fromTables = _queryModel.getFrom().getTableList();
0949: boolean found = false;
0950: for (int j = 0; j < fromTables.size(); j++) {
0951: String fromTableName = ((JoinTable) fromTables
0952: .get(j)).getFullTableName();
0953: // this could be an alias
0954: String fromTableSpec = ((JoinTable) fromTables
0955: .get(j)).getTableSpec();
0956:
0957: if (DEBUG)
0958: System.out.println(" checkColumns called " + // NOI18N
0959: " fromTableName = " + fromTableName + // NOI18N
0960: " fromTableSpec = " + fromTableSpec); // NOI18N
0961: // use the following function to check if fromTableSpec
0962: // is in the database. If it is found update the column.
0963: if (checkColumnNameForTable(column, fromTableSpec)) {
0964: found = true;
0965: break;
0966: }
0967: }
0968: // Give an error only if all the columns have been checked in all tables
0969: if (!found) {
0970: // table not found, give an error
0971: showTableColumnNameError(column.getColumnName());
0972: return false;
0973: }
0974: }
0975:
0976: if (!checkTableColumnName(column)
0977: // Not clear what this test was for; it meant that we only reported an error if
0978: // the column that failed was the last one
0979: // && ( i == ( columns.size() - 1 ) )
0980: ) {
0981: showTableColumnNameError(column.getColumnName());
0982: return false;
0983: }
0984: // the table has an alias, do not check the table name, just
0985: // check column name
0986: }
0987: return true;
0988: }
0989:
0990: /**
0991: * Report an unrecognized table/column name
0992: */
0993: private void showTableColumnNameError(String error) {
0994: String msg = NbBundle.getMessage(QueryBuilder.class,
0995: "TABLE_COLUMN_NAME_ERROR");
0996: NotifyDescriptor d = new NotifyDescriptor.Message(error + " : "
0997: + msg + "\n\n", NotifyDescriptor.ERROR_MESSAGE);
0998: DialogDisplayer.getDefault().notify(d);
0999: _parseErrorMessage = error + " : " + msg + "\n\n";
1000: String query = getSqlText();
1001: disableVisualEditing(query);
1002: }
1003:
1004: /**
1005: * Parse the current query (obtained from the RowSet).
1006: * @param the current query
1007: */
1008: private void parseQuery(String query) throws ParseException {
1009:
1010: Log.getLogger().entering("QueryBuilder", "parseQuery", query); // NOI18N
1011:
1012: // Initialize the QueryModel object if necessary
1013: if (_queryModel == null)
1014: _queryModel = new QueryModel(quoter);
1015:
1016: _queryModel.parse(query);
1017: }
1018:
1019: /**
1020: * Update the command property of the calling VSE, using the current
1021: * contents of the Sql Text.
1022: * Also, record this as the last valid query, for rollback purposes.
1023: */
1024: private void saveSqlCommand() {
1025:
1026: // Done in setText now?
1027: // _queryBuilderPane.getQueryBuilderSqlTextArea().saveLastGoodQuery();
1028:
1029: // Comapare the current SQL text to the existing Command value; don't do the
1030: // setValue operation if there has been no change
1031: String query = getSqlText();
1032: if (!query.equals(getSqlCommand())) {
1033: Log.getLogger().finest(
1034: "QB: setting sql command to: " + query); //NOI18N
1035: setSqlCommand(query);
1036: }
1037: }
1038:
1039: // Wrappers for SqlStatement methods, which are now handled by a combination of
1040: // VisualSqlEditor and DatabaseConnection
1041:
1042: void setSqlCommand(String query) {
1043: // sqlStatement.setCommand(query) ;
1044: vse.setStatement(query);
1045: }
1046:
1047: String getSqlCommand() {
1048: // return sqlStatement.getCommand();
1049: return vse.getStatement();
1050: }
1051:
1052: // JDTODO - use dbconn
1053: String getConnectionInfo() {
1054: // return sqlStatement.getConnectionInfo();
1055: String conn = "";
1056: try {
1057: conn = getConnection().getMetaData().getURL();
1058: } catch (SQLException ex) {
1059: Exceptions.printStackTrace(ex);
1060: }
1061: return conn;
1062: }
1063:
1064: /**
1065: * Return the java.sql.Connection associated with this dbconn, after opening
1066: * it if necessary.
1067: */
1068: Connection getConnection() {
1069:
1070: Connection conn = dbconn.getJDBCConnection();
1071: if (conn == null) {
1072: // Try to (re-) open the connection
1073: ConnectionManager.getDefault().showConnectionDialog(dbconn);
1074: conn = dbconn.getJDBCConnection();
1075: if (conn == null) {
1076: // Connection failed for some reason. User may have cancelled.
1077: // If there's an exception, ConnectionDialog already reported it.
1078: String msg = NbBundle.getMessage(QueryBuilder.class,
1079: "CANNOT_ESTABLISH_CONNECTION"); // NOI18N
1080: NotifyDescriptor d = new NotifyDescriptor.Message(msg
1081: + "\n\n", NotifyDescriptor.ERROR_MESSAGE); // NOI18N
1082: DialogDisplayer.getDefault().notify(d);
1083: }
1084: }
1085: return conn;
1086: }
1087:
1088: // JDTODO - use dbconn, which requires ConnectionManager.disconnect
1089: void closeQB() {
1090: // sqlStatement.close();
1091: }
1092:
1093: // Wrappers for schema methods that are used by other classes in the query builder
1094:
1095: List getColumnNames(String fullTableName) throws SQLException {
1096: return qbMetaData.getColumnNames(fullTableName);
1097: }
1098:
1099: public void getColumnNames(String fullTableName, List columnNames) {
1100: qbMetaData.getColumnNames(fullTableName, columnNames);
1101: }
1102:
1103: List getImportedKeyColumns(String fullTableName)
1104: throws SQLException {
1105: return qbMetaData.getImportedKeyColumns(fullTableName);
1106: }
1107:
1108: List<String> getAllTables() throws SQLException {
1109: return qbMetaData.getAllTables();
1110: }
1111:
1112: List getPrimaryKeys(String fullTableName) throws SQLException {
1113: return qbMetaData.getPrimaryKeys(fullTableName);
1114: }
1115:
1116: List getForeignKeys(String fullTableName) throws SQLException {
1117: return qbMetaData.getForeignKeys(fullTableName);
1118: }
1119:
1120: String[] findForeignKey(String oldFullTableName,
1121: String newFullTableName, List foreignKeys) {
1122: return qbMetaData.findForeignKey(oldFullTableName,
1123: newFullTableName, foreignKeys);
1124: }
1125:
1126: String[] findForeignKey(String fullTableName1, String colName1,
1127: String fullTableName2, String colName2) throws SQLException {
1128: return qbMetaData.findForeignKey(fullTableName1, colName1,
1129: fullTableName2, colName2);
1130: }
1131:
1132: /**
1133: * Execute the specified query against the database
1134: * @param query the query to execute
1135: */
1136: void executeQuery(String query) {
1137:
1138: Log.getLogger().entering("QueryBuilder", "executeQuery", query); // NOI18N
1139:
1140: String sqlCommand = getSqlText(); // why not "query"?
1141: ResultSet result = null; // value to be returned.
1142:
1143: Connection connection = null;
1144: PreparedStatement myStatement = null;
1145:
1146: showBusyCursor(true);
1147:
1148: boolean canExecute = true;
1149:
1150: ParameterMetaData pmd = null;
1151: int paramCount = 0;
1152: try {
1153: connection = getConnection();
1154: if (connection == null) {
1155: canExecute = false;
1156:
1157: } else {
1158:
1159: myStatement = connection.prepareStatement(sqlCommand);
1160: pmd = myStatement.getParameterMetaData();
1161: paramCount = pmd.getParameterCount();
1162: if (DEBUG) {
1163: System.out.println(" Parameter Count = "
1164: + paramCount);
1165: for (int i = 1; i <= paramCount; i++) {
1166: System.out.println(" Parameter Type = "
1167: + pmd.getParameterType(i));
1168: System.out.println(" Parameter Type Name = "
1169: + pmd.getParameterTypeName(i));
1170: }
1171: }
1172: }
1173: } catch (SQLException e) {
1174: reportDatabaseError(e); // NOI18N
1175: canExecute = false;
1176: } catch (AbstractMethodError e) {
1177: // Certain drivers (e.g., Sybase 5.5) can throw Errors because of incompatibility. Catch and report.
1178: Log.getLogger().severe(
1179: "Error occurred when trying to retrieve table information: "
1180: + e); // NOI18N
1181: String title = NbBundle.getMessage(QueryBuilder.class,
1182: "PROCESSING_ERROR");
1183: JOptionPane.showMessageDialog(this , e.toString() + "\n\n",
1184: title, JOptionPane.ERROR_MESSAGE);
1185: canExecute = false;
1186: }
1187:
1188: // Deal with any query parameters if we know about them.
1189: if (canExecute && (_queryModel != null)) {
1190: if (getParseErrorMessage() == null
1191: && _queryModel.isParameterized()) {
1192:
1193: ArrayList list = new ArrayList();
1194: _queryModel.getParameterizedPredicates(list);
1195: String[] parameters = new String[list.size()];
1196: String[] values = new String[list.size()];
1197:
1198: for (int i = 0; i < parameters.length; i++) {
1199: parameters[i] = new String((String) list.get(i));
1200: }
1201: ParameterizedQueryDialog pqDlg = new ParameterizedQueryDialog(
1202: parameters, true);
1203: if (pqDlg.getReturnStatus() == ParameterizedQueryDialog.RETURNED_OK) {
1204:
1205: values = pqDlg.getParameterValues();
1206: try {
1207: for (int i = 0; i < values.length; i++) {
1208: if (DEBUG) {
1209: System.out.println(" command = "
1210: + sqlCommand);
1211: System.out
1212: .println("PreparedStatement i = "
1213: + i
1214: + " values = "
1215: + values[i]);
1216: }
1217: myStatement.setObject(i + 1, values[i], pmd
1218: .getParameterType(i + 1));
1219: }
1220:
1221: } catch (SQLException e) {
1222: if (isUnsupportedFeature(e)) {
1223: Log.getLogger().log(Level.FINE, null, e);
1224: String msg = NbBundle.getMessage(
1225: QueryBuilder.class,
1226: "PARAMETERS_NOT_SUPPORTED");
1227: reportProcessingError(msg);
1228: } else {
1229: reportDatabaseError(e); // NOI18N
1230: }
1231: canExecute = false;
1232:
1233: }
1234: } else {
1235: // cancelled the dialog.
1236: canExecute = false;
1237: }
1238: } else if (paramCount != 0) {
1239: // we have a query which can not be parsed.
1240: ArrayList list = new ArrayList(paramCount);
1241: if (DEBUG) {
1242: System.out.println(" param count = " + paramCount);
1243: System.out.println(" list size = " + list.size());
1244: }
1245: String[] parameters = new String[paramCount];
1246: String[] values = new String[paramCount];
1247: if (DEBUG) {
1248: System.out.println(" parameters size = "
1249: + parameters.length);
1250: System.out.println(" values size = "
1251: + values.length);
1252: }
1253:
1254: for (int i = 0; i < paramCount; i++) {
1255: parameters[i] = new String("Parameter "
1256: + new Integer(i).toString());
1257: }
1258: if (DEBUG) {
1259: for (int i = 0; i < parameters.length; i++) {
1260: System.out.println(" parameter = "
1261: + parameters[i]);
1262: }
1263: }
1264: ParameterizedQueryDialog pqDlg = new ParameterizedQueryDialog(
1265: parameters, true);
1266: // System.out.println(pqDlg.getReturnStatus());
1267: if (pqDlg.getReturnStatus() == ParameterizedQueryDialog.RETURNED_OK) {
1268: values = pqDlg.getParameterValues();
1269:
1270: try {
1271: for (int i = 0; i < values.length; i++) {
1272: if (DEBUG) {
1273: System.out.println(" command = "
1274: + sqlCommand);
1275: System.out
1276: .println("PreparedStatement i = "
1277: + i
1278: + " values = "
1279: + values[i]);
1280: }
1281: myStatement.setObject(i + 1, values[i], pmd
1282: .getParameterType(i + 1));
1283: }
1284:
1285: } catch (SQLException e) {
1286: reportDatabaseError(e); // NOI18N
1287: canExecute = false;
1288: }
1289: } else {
1290: // cancelled the dialog.
1291: canExecute = false;
1292: }
1293: }
1294: }
1295:
1296: // Now execute the query
1297: if (canExecute) {
1298:
1299: try {
1300: boolean hasResults = myStatement.execute();
1301: if (hasResults) {
1302: result = myStatement.getResultSet();
1303: _queryBuilderPane.getQueryBuilderResultTable()
1304: .displayResultSet(result);
1305: result.close();
1306: }
1307:
1308: } catch (SQLException e) {
1309: reportDatabaseError(e); // NOI18N
1310: }
1311: }
1312:
1313: // clean up stuff.
1314: try {
1315: if (myStatement != null) {
1316: myStatement.close();
1317: }
1318: } catch (SQLException se) {
1319: Log.getLogger().finest(
1320: "Error Closing statement: "
1321: + se.getLocalizedMessage()); // NOI18N
1322: }
1323:
1324: showBusyCursor(false);
1325: Log.getLogger().finest(
1326: "Returning from QueryBuilder.executeQuery"); // NOI18N
1327:
1328: }
1329:
1330: /**
1331: * Initialize all the panes based on a new query passed in from the RowSet
1332: */
1333: void generate() {
1334:
1335: Log.getLogger().entering("QueryBuilder", "generate"); // NOI18N
1336:
1337: // Suppress updating of the text pane until we're ready
1338: _updateText = false;
1339: try {
1340:
1341: // Clear the current state. It might be easier to recreate each of
1342: // the panes, as we did at startup.
1343: _queryBuilderPane.clear();
1344: // Generate the Diagram Pane and Grid Pane
1345: this .generateGraph();
1346: } finally {
1347: _updateText = true;
1348: }
1349:
1350: // Generate the Text Pane
1351: // It's not clear whether we should use the passed-in command,
1352: // or generate it from the model
1353: // _queryBuilderSqlTextArea.setText(command);
1354: this .generateText();
1355: }
1356:
1357: /**
1358: * Generate the query graph and tables that correspond to the current query model
1359: */
1360: private void generateGraph() {
1361: // If this is false, graphics and model are disabled. Don't build graph/table.
1362: if (_graphicsEnabled)
1363: _queryBuilderPane.getQueryBuilderGraphFrame()
1364: .generateGraph(_queryModel);
1365: }
1366:
1367: /**
1368: * Build the SQL query from the current model, and update the text pane
1369: */
1370: void generateText() {
1371: // If this is false, graphics and model are disabled. Don't generate text.
1372: if (_graphicsEnabled) {
1373: setSqlText(_queryModel.genText());
1374:
1375: // Update command property except on first time through
1376: if (!firstTimeGenerateText) {
1377: saveSqlCommand();
1378: } else {
1379: firstTimeGenerateText = false;
1380: }
1381: }
1382: }
1383:
1384: // /**
1385: // * Disable everything except the SQL Text Pane
1386: // */
1387: // void disable() {
1388: // }
1389:
1390: // /**
1391: // * Re-enable the panes that were disabled
1392: // */
1393: // void enable() {
1394: // }
1395:
1396: /***************************************************************************
1397: * Accessors/Mutators
1398: **************************************************************************/
1399:
1400: /**
1401: * Return the current query model
1402: */
1403: QueryModel getQueryModel() {
1404: return _queryModel;
1405: }
1406:
1407: /**
1408: * Restore the last good query.
1409: */
1410:
1411: QueryBuilderPane getQueryBuilderPane() {
1412: return (_queryBuilderPane);
1413: }
1414:
1415: public VisualSQLEditor getVisualSQLEditor() {
1416: return vse;
1417: }
1418:
1419: /**
1420: * Get the current SQL Text from the SqlTextArea
1421: */
1422: private String getSqlText() {
1423: return _queryBuilderPane.getQueryBuilderSqlTextArea().getText();
1424: }
1425:
1426: /**
1427: * Set text in the SqlTextArea
1428: * @param text the new text
1429: */
1430: private void setSqlText(String text) {
1431: _queryBuilderPane.getQueryBuilderSqlTextArea().setText(text);
1432: }
1433:
1434: /**
1435: * Returns true if the current query is parameterized
1436: */
1437: private boolean isParameterized() {
1438: return _queryModel.isParameterized();
1439: }
1440:
1441: // Provide access to metadata object (currently used during text generation)
1442: public QueryBuilderMetaData getMetaData() {
1443: return qbMetaData;
1444: }
1445:
1446: // Methods inherited from org.openide.windows.TopComponent
1447:
1448: /**
1449: * About to show. Could have been previously hidden ( e.g.,
1450: * when the user tabs back to the QueryEditor) or on first showing.
1451: *
1452: * We need to flush all the current information (model, graph, metadata),
1453: * and read the command, as if we were starting for the first time.
1454: */
1455: protected void componentShowing() {
1456:
1457: Log.getLogger().entering("QueryBuilder", "componentShowing"); // NOI18N
1458:
1459: String command = getSqlCommand();
1460:
1461: if (_queryModel == null)
1462: _queryModel = new QueryModel(quoter);
1463:
1464: Log.getLogger().finest(" * command=" + command);
1465:
1466: // Parse the current query, in case it has changed
1467:
1468: // Special case for handling null queries -- prompt for an initial table
1469: // We should probably allow this, since the user can delete the last table in the
1470: // editor anyway, so we need to be able to deal with empty queries as a special case.
1471: if ((command == null) || (command.trim().length() == 0)) {
1472: Log.getLogger().finest("QBShowing command is null");
1473: setVisible(true);
1474: this .repaint();
1475: String msg = NbBundle.getMessage(QueryBuilder.class,
1476: "EMPTY_QUERY_ADD_TABLE");
1477: NotifyDescriptor d = new NotifyDescriptor.Message(msg
1478: + "\n\n", NotifyDescriptor.ERROR_MESSAGE); // NOI18N
1479: DialogDisplayer.getDefault().notify(d);
1480:
1481: _queryBuilderPane.getQueryBuilderGraphFrame().addTable();
1482:
1483: } else {
1484:
1485: String queryText = getSqlText();
1486: // parse and populate only if the query has changed.
1487: if (queryText == null
1488: || (!command.trim().equalsIgnoreCase(
1489: queryText.trim()))) {
1490: this .populate(command, false);
1491: setVisible(true);
1492: this .repaint();
1493: }
1494: }
1495: activateActions();
1496:
1497: _queryBuilderPane.getQueryBuilderSqlTextArea().requestFocus();
1498:
1499: if (DEBUG)
1500: System.out
1501: .println(" _queryBuilderPane.getQueryBuilderSqlTextArea().requestFocus () called. "); // NOI18N
1502:
1503: }
1504:
1505: /**
1506: * Component is about to be shown.
1507: * Called when the user moves to another tab.
1508: * If we have an associated rowset, update it with current text query.
1509: */
1510: @Override
1511: protected void componentHidden() {
1512: Log.getLogger().entering("QueryBuilder", "componentHidden");
1513: String command = getSqlCommand();
1514: if ((command != null) && (command.trim().length() != 0)) {
1515: String queryText = getSqlText();
1516:
1517: // parse and populate only if the query has changed.
1518: if (queryText == null
1519: || (!command.trim().equalsIgnoreCase(
1520: queryText.trim()))) {
1521: if (_graphicsEnabled) {
1522: boolean good = this .populate(queryText, true);
1523: if (!good) {
1524: setSqlCommand(queryText); //HACK, temporary (jfb)
1525: }
1526: } else {
1527: setSqlCommand(queryText);
1528: }
1529: }
1530: }
1531: deactivateActions();
1532: }
1533:
1534: /** Opened for the first time */
1535: @Override
1536: protected void componentOpened() {
1537:
1538: Log.getLogger().entering("QueryBuilder", "componentOpened");
1539:
1540: activateActions();
1541: ActionMap map = getActionMap();
1542: InputMap keys = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
1543: installActions(map, keys);
1544:
1545: // QueryBuilder does not need to listen to VSE, because it will notify us
1546: // directly if something changes. The SqlCommandCustomizer needs to listen
1547: // to VSE, because that's the only way it is notified of changes to the command
1548: // sqlStatement.addPropertyChangeListener(sqlStatementListener) ;
1549: // vse.addPropertyChangeListener(sqlStatementListener) ;
1550:
1551: // do NOT force a parse here. It's done in componentShowing().
1552: // populate( sqlStatement.getCommand()) ;
1553: }
1554:
1555: /* closed - not visible anywhere)
1556: */
1557: @Override
1558: protected void componentClosed() {
1559: Log.getLogger().entering("QueryBuilder", "componentClosed");
1560:
1561: deactivateActions();
1562:
1563: // JDTODO - use dbconn?
1564: this .closeQB();
1565: lastQuery = null;
1566: }
1567:
1568: /*****
1569: * listener for changes in the sqlStatement - either the
1570: * command changed or the connection changed (e.g., datasource changed).
1571: */
1572: // No longer needed, since the customizer will just call vse.setStatement,
1573: // which can in turn update the QueryEditor
1574: // private PropertyChangeListener sqlStatementListener = new PropertyChangeListener() {
1575: // public void propertyChange(PropertyChangeEvent evt) {
1576: // // what property?
1577: // String propName = evt.getPropertyName() ;
1578: // Log.getLogger().finest("QB sqlStatement property change: " + propName ) ;
1579: // if ( propName.equals(VisualSQLEditor.PROP_STATEMENT)) {
1580: // Log.getLogger().finest(" newValue=" + getSqlCommand()) ;
1581: // populate( getSqlCommand() ) ;
1582: // _queryBuilderPane.getQueryBuilderSqlTextArea().requestFocus();
1583: //
1584: // } else if ( propName.equals(SqlStatement.TITLE) ) {
1585: // Log.getLogger().finest(" title to " + sqlStatement.getTitle()) ; // NOI18N
1586: // SwingUtilities.invokeLater(new Runnable() {
1587: // public void run() {
1588: // setDisplayName(sqlStatement.getTitle()) ;
1589: // }
1590: // }) ;
1591: // } else if ( propName.equals(SqlStatement.CLOSING)) {
1592: // Log.getLogger().finest(" closing...") ; // NOI18N
1593: // SwingUtilities.invokeLater(new Runnable() {
1594: // public void run() {
1595: // close() ;
1596: // }
1597: // }) ;
1598: // }
1599: // }
1600: // } ;
1601: /***
1602: private final JButton retryButton = new JButton(NbBundle.getMessage(QueryBuilder.class, "RETRY_AND_CONTINUE")) ;
1603: private final JButton cancelButton = new JButton(NbBundle.getMessage(QueryBuilder.class, "CANCEL_AND_CONTINUE")) ;
1604: **/
1605: /******
1606: * show a dialog with the a message saying the database connection is hosed.
1607: * It has a Retry and Cancel button.
1608: * returns true if the Retry is the closing action.
1609: */
1610: // int ii = 0 ;
1611: // private boolean showRetryDialog() {
1612: // Log.getLogger().entering("QueryBuilder", "showRetryDialog", ii++);
1613: // ConnectionStatusPanel csp = new ConnectionStatusPanel() ;
1614: // csp.configureDisplay(getConnectionInfo(), false, lastException.getLocalizedMessage(), "", 0, false ) ;
1615: // csp.setGeneralInfo(NbBundle.getMessage(QueryBuilder.class, "DATABASE_CONNECTION_ERROR") ) ; // NOI18N
1616: // csp.setFooterInfo(NbBundle.getMessage(QueryBuilder.class, "NO_DATABASE_CONNECTION") ) ; // NOI18N
1617: //
1618: // final JButton retryButton = new JButton(NbBundle.getMessage(QueryBuilder.class, "RETRY_AND_CONTINUE")) ;
1619: // final JButton cancelButton = new JButton(NbBundle.getMessage(QueryBuilder.class, "CANCEL_AND_CONTINUE")) ;
1620: //
1621: // // this listener is for the dialog.
1622: // final Object[] retVal = new Object[1] ;
1623: // ActionListener listener = new ActionListener() {
1624: // public void actionPerformed(java.awt.event.ActionEvent evt) {
1625: // Log.getLogger().finest(" retry dialog event: " + evt) ;
1626: // retVal[0] = evt.getSource() ;
1627: // }
1628: //
1629: // };
1630: //
1631: // DialogDescriptor dlg = new DialogDescriptor(csp,
1632: // NbBundle.getMessage(ConnectionStatusPanel.class, "ConStat_title", getConnectionInfo()), // NOI18N
1633: // true/*modal*/,
1634: // new Object[] {retryButton, cancelButton}, cancelButton,
1635: // DialogDescriptor.DEFAULT_ALIGN, null, listener);
1636: //
1637: // dlg.setClosingOptions( null );
1638: //
1639: // Dialog dialog = DialogDisplayer.getDefault().createDialog(dlg);
1640: // dialog.setResizable(true);
1641: // dialog.pack() ;
1642: //
1643: // // present dialog, waits for it to be disposed.
1644: // dialog.show();
1645: //
1646: // boolean val = ( retVal[0] == retryButton ) ;
1647: // Log.getLogger().finest(" * dlg says: Retry=" + val ) ;
1648: // return val ;
1649: // }
1650:
1651: private boolean isUnsupportedFeature(SQLException e) {
1652: return e.getErrorCode() == 17023 || // Oracle "Unsupported feature" error
1653: "S1C00".equals(e.getSQLState()); // MySQL "Optional feature not supported" error
1654: }
1655:
1656: private void reportProcessingError(String msg) {
1657: // NbBundle.getMessage(QueryBuilder.class, key);
1658: String title = NbBundle.getMessage(QueryBuilder.class,
1659: "PROCESSING_ERROR");
1660:
1661: JOptionPane.showMessageDialog(this , msg + "\n\n", title,
1662: JOptionPane.ERROR_MESSAGE);
1663: }
1664:
1665: private void reportDatabaseError(SQLException e) {
1666:
1667: Log.getLogger().log(Level.FINE, null, e);
1668:
1669: String msg = isUnsupportedFeature(e) ? NbBundle.getMessage(
1670: QueryBuilder.class, "UNSUPPORTED_FEATURE") : e
1671: .getLocalizedMessage();
1672:
1673: reportProcessingError(msg);
1674:
1675: /*
1676: ConnectionStatusPanel csp = new ConnectionStatusPanel() ;
1677: csp.configureDisplay(sqlStatement.getConnectionInfo(), false, e.getLocalizedMessage(), "", 0, false ) ;
1678: csp.setGeneralInfo(msg) ;
1679: csp.displayDialog( sqlStatement.getConnectionInfo() ) ;
1680: */
1681:
1682: }
1683:
1684: String getParseErrorMessage() {
1685: return _parseErrorMessage;
1686: }
1687:
1688: /**
1689: * Showing/hiding busy cursor, before this funcionality was in Rave winsys,
1690: * the code is copied from that module.
1691: * It needs to be called from event-dispatching thread to work synch,
1692: * otherwise it is scheduled into that thread. */
1693: static void showBusyCursor(final boolean busy) {
1694: if (SwingUtilities.isEventDispatchThread()) {
1695: doShowBusyCursor(busy);
1696: } else {
1697: SwingUtilities.invokeLater(new Runnable() {
1698: public void run() {
1699: doShowBusyCursor(busy);
1700: }
1701: });
1702: }
1703: }
1704:
1705: private static void doShowBusyCursor(boolean busy) {
1706: JFrame mainWindow = (JFrame) WindowManager.getDefault()
1707: .getMainWindow();
1708: if (busy) {
1709: RepaintManager.currentManager(mainWindow)
1710: .paintDirtyRegions();
1711: mainWindow.getGlassPane().setCursor(
1712: Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
1713: mainWindow.getGlassPane().setVisible(true);
1714: mainWindow.repaint();
1715: } else {
1716: mainWindow.getGlassPane().setVisible(false);
1717: mainWindow.getGlassPane().setCursor(null);
1718: mainWindow.repaint();
1719: }
1720: }
1721:
1722: public HelpCtx getHelpCtx() {
1723: return new HelpCtx(
1724: "projrave_ui_elements_editors_about_query_editor"); // NOI18N
1725: }
1726:
1727: }
|