├── .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 | 
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: [](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 | 
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 |
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 |
--------------------------------------------------------------------------------