├── .gitignore ├── CHANGELOG ├── LICENSE ├── META-INF └── bundle.xml ├── README.md ├── SQL Developer 4 keepalive.jws ├── SQL Developer 4 keepalive ├── SQL Developer 4 keepalive.jpr └── src │ ├── META-INF │ ├── MANIFEST.MF │ └── extension.xml │ └── keepalive │ ├── ConnectionPinger.java │ ├── KeepaliveController.java │ ├── PollIntervalDialog.java │ └── Res.properties ├── keepalive-icon.png └── pack_extension.sh /.gitignore: -------------------------------------------------------------------------------- 1 | SQL Developer 4 keepalive/deploy 2 | SQL Developer 4 keepalive/classes 3 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | *** VERSION 1.3 TO 1.3.1 2 | - Added additional database compatibility 3 | 4 | *** VERSION 1.2 TO 1.3 5 | - Merged fix on open cursors from Github user jdiazest's fork 6 | - Extension icon changed 7 | - Added script for building the extension ZIP 8 | 9 | *** VERSION 1.0 TO 1.2 10 | - The logging is no longer interfering with user input 11 | - Solved other minor issues (please refer to the issue tracker on Github) 12 | - Merged new input dialog from Github user eftewuer's fork 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2020 Stefano Cristalli 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /META-INF/bundle.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | keepalive 6 | 1.3.1 7 | Stefano Cristalli 8 | http://scristalli.altervista.org 9 | keepalive extension for Oracle SQL Developer 4+. 10 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Keepalive extension for SQL Developer # 2 | 3 | ![GitHub All Releases](https://img.shields.io/github/downloads/scristalli/sql-developer-keepalive/total) 4 | 5 | ## What is this? 6 | You know when you are working in SQL Developer, and you have to reconnect because your connection has been terminated? 7 | Pretty annoying, right? 8 | This extension solves your problem! By sending a simple query to the database every _n_ seconds, it keeps your connection alive. 9 | 10 | ## How do I install it? 11 | Just download the latest release: [![GitHub release (latest by date)](https://img.shields.io/github/v/release/scristalli/sql-developer-keepalive)](https://github.com/scristalli/sql-developer-keepalive/releases) 12 | It comes as a ZIP, which you can simply install in SQL Developer with the menu _Help > Check for Updates_. 13 | After restarting SQL developer, click the first icon from the right to start using the keepalive. 14 | ![keepalive icon](https://raw.githubusercontent.com/scristalli/sql-developer-keepalive/master/keepalive-icon.png) 15 | Something is not clear? Read two sections ahead ;) 16 | 17 | ## Why did you make this? 18 | The community wanted this. I started from [MinChen Chai's extension for SQL Developer 3](https://sites.google.com/site/keepaliveext/) and ported it to the OSGi framework used from version 4. 19 | 20 | ## Can I contribute? 21 | Sure! 22 | * Something doesn't work? Please open an [issue](https://github.com/scristalli/sql-developer-keepalive/issues). 23 | * Want to document something better? Please edit the [wiki](https://github.com/scristalli/sql-developer-keepalive/wiki). 24 | * Want to improve the extension? Please open a [pull request](https://github.com/scristalli/sql-developer-keepalive/compare). 25 | * Want to say thanks or to help me develop more software? Read the next section ;) 26 | 27 | ## Is your software free? 28 | It is, and released under the [MIT license](https://github.com/scristalli/sql-developer-keepalive/blob/master/LICENSE). 29 | However, it would be nice to have an incentive to do other little projects. [Buy me a coffee](https://buymeacoffee.com/scristalli) and I'll be grateful! 30 | -------------------------------------------------------------------------------- /SQL Developer 4 keepalive.jws: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /SQL Developer 4 keepalive/SQL Developer 4 keepalive.jpr: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | -------------------------------------------------------------------------------- /SQL Developer 4 keepalive/src/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Bundle-ManifestVersion: 2.0 2 | Bundle-Version: 1.3.1 3 | Bundle-SymbolicName: keepalive 4 | Bundle-ClassPath: . 5 | Require-Bundle: oracle.ide, 6 | oracle.icons, 7 | oracle.sqldeveloper, 8 | oracle.javatools, 9 | oracle.db-api 10 | -------------------------------------------------------------------------------- /SQL Developer 4 keepalive/src/META-INF/extension.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | SQL Developer 4 keepalive 5 | Stefano Cristalli 6 | 7 | 8 | 9 | 10 | 11 | 12 | &keepalive 13 | ${OracleIcons.STATUS_ICONS} 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
32 | 33 |
34 |
35 |
36 |
37 |
38 |
39 | 40 | 41 | keepalive extension for Oracle SQL Developer 4+. 42 | true 43 | 44 | 45 |
46 | -------------------------------------------------------------------------------- /SQL Developer 4 keepalive/src/keepalive/ConnectionPinger.java: -------------------------------------------------------------------------------- 1 | package keepalive; 2 | 3 | import java.sql.ResultSet; 4 | import java.sql.SQLException; 5 | import java.sql.Statement; 6 | import java.text.DateFormat; 7 | import java.text.SimpleDateFormat; 8 | import java.util.Date; 9 | 10 | import oracle.dbtools.raptor.utils.Connections; 11 | import oracle.ide.log.LogManager; 12 | 13 | public class ConnectionPinger implements Runnable { 14 | 15 | private boolean execute; 16 | private int interval; 17 | 18 | public ConnectionPinger(Integer interval) { 19 | try { 20 | this.interval = interval; 21 | if (this.interval < 60) { 22 | LogMessage("WARNING", "Timeout is too low (less than 60 seconds)."); 23 | LogMessage("INFO", "Timeout set to default (600 seconds)."); 24 | this.interval = 600; 25 | } else { 26 | LogMessage("INFO", "Timeout set to " + this.interval + " seconds."); 27 | } 28 | } catch (Exception e) { 29 | LogMessage("WARNING", "Error while setting timeout."); 30 | LogMessage("INFO", "Timeout set to default (600 seconds)."); 31 | this.interval = 600; 32 | } 33 | } 34 | 35 | private static final void LogMessage(String level, String msg) { 36 | DateFormat dateFormat = new SimpleDateFormat("HH:mm:ss"); 37 | Date date = new Date(); 38 | String currentTime = "[" + dateFormat.format(date) + "] "; 39 | LogManager.getLogManager().getMsgPage().log("KEEPALIVE " + currentTime + level + ": " + msg + "\n"); 40 | } 41 | 42 | @Override 43 | public void run() { 44 | this.execute = true; 45 | LogMessage("INFO", "keepalive started."); 46 | while (this.execute) { 47 | try { 48 | String[] l = Connections.getInstance().getConnNames(); 49 | LogMessage("INFO", "keepalive event triggered, scanning " + l.length + " connections..."); 50 | for (int i = 0; i < l.length; i++) { 51 | String connectionName = l[i]; 52 | String displayConnectionName = connectionName; 53 | if (displayConnectionName.contains("%23")) { 54 | displayConnectionName = connectionName.substring(connectionName.indexOf("%23") + 3); 55 | } 56 | if (Connections.getInstance().isConnectionOpen(connectionName)) { 57 | String QueryString = ""; 58 | Statement sqlStatement = null; 59 | try { 60 | sqlStatement = Connections.getInstance().getConnection(connectionName).createStatement(); 61 | String productName = Connections.getInstance().getConnection(connectionName).getMetaData().getDatabaseProductName(); 62 | switch (productName) { 63 | case "Microsoft SQL Server": 64 | QueryString = "SELECT 1 AS RESULT"; 65 | break; 66 | case "Oracle": 67 | QueryString = "SELECT SYSDATE AS RESULT FROM DUAL"; 68 | break; 69 | default: 70 | LogMessage("DEBUG", "Database product name: " + productName); 71 | QueryString = "SELECT 1 AS RESULT"; 72 | } 73 | 74 | ResultSet rs = sqlStatement.executeQuery(QueryString); 75 | while (rs.next()) { 76 | LogMessage("DEBUG", "The keepalive query returned: " + rs.getString("RESULT")); 77 | } 78 | LogMessage("INFO", displayConnectionName + " successfully kept alive!"); 79 | } catch (Exception e) { 80 | LogMessage("ERROR", "The keepalive query was: " + QueryString); 81 | LogMessage("ERROR", e.getMessage()); 82 | } finally { 83 | if (sqlStatement != null) { 84 | try { 85 | sqlStatement.close(); 86 | } catch (SQLException e) { 87 | LogMessage("ERROR", e.getMessage()); 88 | } 89 | } 90 | } 91 | } else { 92 | // NO ACTION (the connection is not open) 93 | } 94 | } 95 | LogMessage("INFO", "keepalive event finished, next event in " + this.interval + " seconds."); 96 | Thread.sleep(this.interval * 1000); 97 | } catch (InterruptedException e) { 98 | this.execute = false; 99 | LogMessage("INFO", "keepalive stopped."); 100 | } catch (Exception e) { 101 | LogMessage("ERROR", e.getMessage()); 102 | // for other errors we can continue pinging connections 103 | // but sleep the thread to avoid infinite loop pinging the connection thousands times a second 104 | try { 105 | Thread.sleep(this.interval * 1000); 106 | } catch (InterruptedException f) { 107 | this.execute = false; 108 | LogMessage("INFO", "keepalive stopped."); 109 | } 110 | } 111 | 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /SQL Developer 4 keepalive/src/keepalive/KeepaliveController.java: -------------------------------------------------------------------------------- 1 | package keepalive; 2 | 3 | import oracle.ide.Context; 4 | import oracle.ide.controller.Controller; 5 | import oracle.ide.controller.IdeAction; 6 | 7 | public class KeepaliveController implements Controller { 8 | 9 | private static boolean started = false; 10 | private Thread keeper = null; 11 | 12 | @Override 13 | public boolean handleEvent(IdeAction ideAction, Context context) { 14 | if (!started) { 15 | //String timeout = JOptionPane.showInputDialog(null, "Please input the desired timeout (in seconds)"); 16 | PollIntervalDialog dlg = new PollIntervalDialog(null); 17 | dlg.setVisible(true); 18 | 19 | Integer pollInterval = dlg.getPollInterval(); 20 | if(pollInterval != null) { 21 | started = true; 22 | ConnectionPinger conn = new ConnectionPinger(pollInterval); 23 | keeper = new Thread(conn); 24 | keeper.start(); 25 | } 26 | } else { 27 | started = false; 28 | keeper.interrupt(); 29 | } 30 | return true; 31 | } 32 | 33 | @Override 34 | public boolean update(IdeAction ideAction, Context context) { 35 | return true; 36 | } 37 | } -------------------------------------------------------------------------------- /SQL Developer 4 keepalive/src/keepalive/PollIntervalDialog.java: -------------------------------------------------------------------------------- 1 | package keepalive; 2 | 3 | import java.awt.event.ComponentAdapter; 4 | import java.awt.event.ComponentEvent; 5 | 6 | import java.beans.PropertyChangeEvent; 7 | import java.beans.PropertyChangeListener; 8 | 9 | import java.util.regex.Matcher; 10 | import java.util.regex.Pattern; 11 | 12 | import javax.swing.JDialog; 13 | import javax.swing.JFrame; 14 | import javax.swing.JOptionPane; 15 | import javax.swing.JTextField; 16 | import javax.swing.text.AttributeSet; 17 | import javax.swing.text.BadLocationException; 18 | import javax.swing.text.DocumentFilter; 19 | import javax.swing.text.PlainDocument; 20 | 21 | public class PollIntervalDialog extends JDialog { 22 | @SuppressWarnings("compatibility:-4837128555563361740") 23 | private static final long serialVersionUID = 931087028121895189L; 24 | 25 | private JTextField textField; 26 | private JOptionPane optionPane; 27 | private Integer pollInterval; 28 | 29 | // minimal value for poll interval in seconds 30 | private final Integer minPollInterval = 60; 31 | 32 | // maximal value for poll interval in seconds 33 | private final Integer maxPollInterval = 43200; //12h 34 | 35 | private static final String OK_STRING = "OK"; 36 | private static final String CANCEL_STRING = "Cancel"; 37 | 38 | public PollIntervalDialog(JFrame parent) { 39 | super(parent, true); 40 | setTitle("keepalive"); 41 | initDialog(); 42 | pack(); 43 | } 44 | 45 | private void initDialog() { 46 | textField = new JTextField(20); 47 | PlainDocument doc = (PlainDocument) textField.getDocument(); 48 | doc.setDocumentFilter(new NumberFilter()); 49 | 50 | Object[] controls = { "Specify poll interval (in seconds):", textField }; 51 | Object[] options = { OK_STRING, CANCEL_STRING }; 52 | 53 | optionPane = new JOptionPane(controls, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_OPTION, null, options, options[0]); 54 | setContentPane(optionPane); 55 | 56 | addComponentListener(new ComponentAdapter() { 57 | @Override 58 | public void componentShown(ComponentEvent ce) { 59 | textField.requestFocusInWindow(); 60 | } 61 | }); 62 | 63 | optionPane.addPropertyChangeListener(new PropertyChangeListener() { 64 | @Override 65 | public void propertyChange(PropertyChangeEvent e) { 66 | 67 | if (isVisible() && JOptionPane.VALUE_PROPERTY.equals(e.getPropertyName())) { 68 | 69 | Object value = optionPane.getValue(); 70 | 71 | if (value == JOptionPane.UNINITIALIZED_VALUE) { 72 | return; 73 | } 74 | 75 | switch (value.toString()) { 76 | case OK_STRING: 77 | String v = textField.getText(); 78 | if (checkValue(v)) { 79 | pollInterval = Integer.parseInt(v, 10); 80 | setVisible(false); 81 | } else { 82 | //to let PropertyChangeEvent fire next time the user presses button 83 | optionPane.setValue(JOptionPane.UNINITIALIZED_VALUE); 84 | } 85 | break; 86 | 87 | case CANCEL_STRING: 88 | pollInterval = null; 89 | setVisible(false); 90 | break; 91 | } 92 | } 93 | } 94 | 95 | }); 96 | } 97 | 98 | private boolean checkValue(String txt) { 99 | if (txt != null && txt.length() > 0) { 100 | //avoid parsing possibly very long string into integer 101 | if (txt.length() <= maxPollInterval.toString().length()) { 102 | try { 103 | Integer value = Integer.parseInt(txt, 10); 104 | if (value >= minPollInterval && value <= maxPollInterval) { 105 | return true; 106 | } 107 | } catch (NumberFormatException e) { 108 | } 109 | } 110 | } 111 | 112 | JOptionPane.showMessageDialog(this, "Please specify value in range " + minPollInterval + " - " + maxPollInterval, "Wrong value", JOptionPane.ERROR_MESSAGE); 113 | return false; 114 | } 115 | 116 | public Integer getPollInterval() { 117 | return pollInterval; 118 | } 119 | } 120 | 121 | class NumberFilter extends DocumentFilter 122 | { 123 | private static final Pattern pattern = Pattern.compile("^[0-9]+$"); 124 | 125 | @Override 126 | public void replace(DocumentFilter.FilterBypass fb, int offset, int length, String text, 127 | AttributeSet attrs) throws BadLocationException { 128 | if (isNumber(text)) 129 | super.replace(fb, offset, length, text, attrs); 130 | else { 131 | java.awt.Toolkit.getDefaultToolkit().beep(); 132 | } 133 | } 134 | 135 | @Override 136 | public void insertString(DocumentFilter.FilterBypass fb, int offset, String text, AttributeSet attr) throws BadLocationException { 137 | if (isNumber(text)) { 138 | super.insertString(fb, offset, text, attr); 139 | } else { 140 | java.awt.Toolkit.getDefaultToolkit().beep(); 141 | } 142 | } 143 | 144 | private static boolean isNumber(String s) { 145 | Matcher m = pattern.matcher(s); 146 | return m.matches(); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /SQL Developer 4 keepalive/src/keepalive/Res.properties: -------------------------------------------------------------------------------- 1 | # Translatable resources for extension keepalive 2 | 3 | EXTENSION_NAME=SQL Developer 4 keepalive 4 | EXTENSION_OWNER=Stefano Cristalli 5 | 6 | -------------------------------------------------------------------------------- /keepalive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scristalli/sql-developer-keepalive/7471646279b4bfc104cd6a1cb4230a8648be0fec/keepalive-icon.png -------------------------------------------------------------------------------- /pack_extension.sh: -------------------------------------------------------------------------------- 1 | cd $(dirname $0) 2 | cd "SQL Developer 4 keepalive" 3 | 4 | if [ -f deploy/keepalive.jar ] 5 | then 6 | mkdir deploy/META-INF 7 | cp ../META-INF/bundle.xml deploy/META-INF/bundle.xml 8 | cd deploy 9 | zip keepalive.zip -r * 10 | rm -r META-INF 11 | else 12 | echo "keepalive.jar is missing" 13 | fi 14 | --------------------------------------------------------------------------------