1
1
package it .renvins .serverpulse .service .impl ;
2
2
3
+ import java .net .SocketTimeoutException ;
4
+ import java .net .URI ;
5
+ import java .net .http .HttpClient ;
6
+ import java .net .http .HttpRequest ;
7
+ import java .net .http .HttpResponse ;
8
+ import java .time .Duration ;
3
9
import java .util .logging .Level ;
4
10
5
11
import com .influxdb .client .InfluxDBClient ;
11
17
import it .renvins .serverpulse .service .IDatabaseService ;
12
18
import lombok .Getter ;
13
19
import org .bukkit .configuration .ConfigurationSection ;
20
+ import org .bukkit .scheduler .BukkitTask ;
14
21
15
22
public class DatabaseService implements IDatabaseService {
16
23
@@ -20,68 +27,269 @@ public class DatabaseService implements IDatabaseService {
20
27
@ Getter private InfluxDBClient client ;
21
28
@ Getter private WriteApi writeApi ;
22
29
30
+ private HttpClient httpClient ; // Keep for ping
31
+ private volatile BukkitTask retryTask ; // volatile for visibility across threads
32
+
33
+ private final int MAX_RETRIES = 5 ;
34
+ private final long RETRY_DELAY_TICKS = 20L * 30L ; // 30 seconds
35
+
36
+ // Use volatile as this is read/written by different threads
37
+ private volatile boolean isConnected = false ;
38
+ private volatile int retryCount = 0 ;
39
+
23
40
public DatabaseService (ServerPulsePlugin plugin , CustomConfig customConfig ) {
24
41
this .plugin = plugin ;
25
42
this .customConfig = customConfig ;
43
+ this .httpClient = HttpClient .newBuilder ()
44
+ .connectTimeout (Duration .ofSeconds (10 ))
45
+ .build ();
26
46
}
27
47
28
48
@ Override
29
49
public void load () {
30
50
if (!checkConnectionData ()) {
31
- ServerPulseLoader .LOGGER .severe ("InfluxDB connection data is missing or wrong, shutting down the plugin..." );
51
+ ServerPulseLoader .LOGGER .severe ("InfluxDB connection data is missing or invalid. Disabling plugin..." );
32
52
plugin .getServer ().getPluginManager ().disablePlugin (plugin );
33
53
return ;
34
54
}
35
- connect ( );
55
+ plugin . getServer (). getScheduler (). runTaskAsynchronously ( plugin , this :: connect );
36
56
}
37
57
38
58
@ Override
39
59
public void unload () {
40
- if (writeApi != null ) {
41
- writeApi .close ();
42
- ServerPulseLoader .LOGGER .info ("Write API closed successfully..." );
60
+ stopRetryTask (); // Stop retries before disconnecting
61
+ disconnect ();
62
+ if (httpClient != null ) {
63
+ httpClient .close ();
43
64
}
44
- if (client != null ) {
45
- client .close ();
46
- ServerPulseLoader .LOGGER .info ("InfluxDB client closed successfully..." );
65
+ }
66
+
67
+ @ Override
68
+ public boolean ping () {
69
+ String url = customConfig .getConfig ().getString ("metrics.influxdb.url" );
70
+ if (url == null || url .isEmpty ()) {
71
+ ServerPulseLoader .LOGGER .severe ("InfluxDB URL is missing for ping..." );
72
+ return false ;
73
+ }
74
+
75
+ // Ensure httpClient is initialized
76
+ if (this .httpClient == null ) {
77
+ ServerPulseLoader .LOGGER .severe ("HttpClient not initialized for ping..." );
78
+ this .httpClient = HttpClient .newBuilder ().connectTimeout (Duration .ofSeconds (10 )).build ();
79
+ }
80
+
81
+ HttpRequest request ;
82
+ try {
83
+ String pingUrl = url .endsWith ("/" ) ? url + "ping" : url + "/ping" ;
84
+ request = HttpRequest .newBuilder ()
85
+ .uri (URI .create (pingUrl ))
86
+ .GET ()
87
+ .timeout (Duration .ofSeconds (5 )) // Add timeout specific to ping
88
+ .build ();
89
+ } catch (IllegalArgumentException e ) {
90
+ ServerPulseLoader .LOGGER .log (Level .SEVERE , "Invalid InfluxDB URL format for ping: " + url , e );
91
+ return false ;
92
+ }
93
+
94
+ try {
95
+ HttpResponse <Void > response = this .httpClient .send (request , HttpResponse .BodyHandlers .discarding ());
96
+ return response .statusCode () == 204 ;
97
+ } catch (java .net .ConnectException | java .net .UnknownHostException e ) {
98
+ ServerPulseLoader .LOGGER .warning ("InfluxDB service is offline..." );
99
+ return false ;
100
+ } catch (
101
+ SocketTimeoutException e ) {
102
+ ServerPulseLoader .LOGGER .warning ("InfluxDB ping timed out: " + e .getMessage ());
103
+ return false ;
104
+ } catch (Exception e ) {
105
+ ServerPulseLoader .LOGGER .log (Level .SEVERE , "Error during InfluxDB ping: " + e .getMessage (), e );
106
+ return false ;
47
107
}
48
108
}
49
109
110
+ @ Override
111
+ public boolean isConnected () {
112
+ // Return the flag, don't ping here!
113
+ return this .isConnected ;
114
+ }
115
+
50
116
/**
51
- * Establishes the connection to the InfluxDB instance using details
52
- * from the configuration. Disables the plugin if the connection fails.
117
+ * Attempts to connect to InfluxDB. Updates the internal connection status
118
+ * and starts the retry task if connection fails.
119
+ * Should be run asynchronously.
53
120
*/
54
121
private void connect () {
122
+ // If already connected, don't try again unless forced (e.g., by retry task)
123
+ // Note: This check might prevent the retry task from running if isConnected is true
124
+ // but the connection actually dropped without detection. We rely on ping() inside here.
125
+
126
+ // Ensure previous resources are closed if attempting a new connection
127
+ // This might be needed if connect() is called manually or by retry.
128
+ disconnect ();
129
+
55
130
ConfigurationSection section = customConfig .getConfig ().getConfigurationSection ("metrics.influxdb" );
131
+ if (section == null ) {
132
+ ServerPulseLoader .LOGGER .severe ("InfluxDB config section missing during connect attempt..." );
133
+ return ;
134
+ }
135
+ String url = section .getString ("url" );
136
+ String token = section .getString ("token" );
137
+ String org = section .getString ("org" );
138
+ String bucket = section .getString ("bucket" );
139
+
56
140
try {
57
- client = InfluxDBClientFactory .create (section .getString ("url" ), section .getString ("token" ).toCharArray (),
58
- section .getString ("org" ), section .getString ("bucket" ));
59
- writeApi = client .makeWriteApi ();
141
+ ServerPulseLoader .LOGGER .info ("Attempting to connect to InfluxDB at " + url );
142
+ client = InfluxDBClientFactory .create (url , token .toCharArray (), org , bucket );
60
143
61
- ServerPulseLoader .LOGGER .info ("Connected successfully to InfluxDB..." );
144
+ // Ping immediately after creating client to verify reachability & auth
145
+ boolean isPingSuccessful = ping (); // Use the internal ping method
146
+
147
+ if (isPingSuccessful ) {
148
+ writeApi = client .makeWriteApi (); // Initialize Write API
149
+
150
+ this .isConnected = true ;
151
+ this .retryCount = 0 ; // Reset retry count on successful connection
152
+
153
+ stopRetryTask (); // Stop retrying if we just connected
154
+ ServerPulseLoader .LOGGER .info ("Successfully connected to InfluxDB and ping successful..." );
155
+ } else {
156
+ // Ping failed after client creation
157
+ ServerPulseLoader .LOGGER .warning ("Created InfluxDB instance, but ping failed. Will retry..." );
158
+ this .isConnected = false ; // Ensure status is false
159
+
160
+ if (client != null ) {
161
+ client .close (); // Close the client if ping failed
162
+ client = null ;
163
+ }
164
+ startRetryTaskIfNeeded (); // Start retry task
165
+ }
62
166
} catch (Exception e ) {
63
- ServerPulseLoader .LOGGER .log (Level .SEVERE , "Could not connect to InfluxDB, shutting down the plugin..." , e );
64
- plugin .getServer ().getPluginManager ().disablePlugin (plugin );
167
+ // Handle exceptions during InfluxDBClientFactory.create() or ping()
168
+ ServerPulseLoader .LOGGER .log (Level .SEVERE , "Failed to connect or ping InfluxDB: " + e .getMessage ());
169
+ this .isConnected = false ;
170
+ if (client != null ) { // Ensure client is closed on exception
171
+ client .close ();
172
+ client = null ;
173
+ }
174
+ startRetryTaskIfNeeded (); // Start retry task
175
+ }
176
+ }
177
+
178
+ @ Override
179
+ public void disconnect () {
180
+ if (writeApi != null ) {
181
+ try {
182
+ writeApi .close ();
183
+ } catch (Exception e ) {
184
+ ServerPulseLoader .LOGGER .log (Level .WARNING , "Error closing InfluxDB WriteApi..." , e );
185
+ }
186
+ writeApi = null ;
187
+ }
188
+ if (client != null ) {
189
+ try {
190
+ client .close ();
191
+ } catch (Exception e ) {
192
+ ServerPulseLoader .LOGGER .log (Level .WARNING , "Error closing InfluxDB Client..." , e );
193
+ }
194
+ client = null ;
195
+ }
196
+ this .isConnected = false ;
197
+ }
198
+
199
+
200
+ @ Override
201
+ public synchronized void startRetryTaskIfNeeded () {
202
+ if (retryTask != null && !retryTask .isCancelled ()) {
203
+ return ;
204
+ }
205
+ if (!plugin .isEnabled ()) {
206
+ ServerPulseLoader .LOGGER .warning ("Plugin disabling, not starting retry task..." );
207
+ return ;
65
208
}
209
+
210
+ // Reset retry count ONLY when starting the task sequence
211
+ this .retryCount = 0 ;
212
+ ServerPulseLoader .LOGGER .warning ("Connection failed. Starting connection retry task (Max " + MAX_RETRIES + " attempts)..." );
213
+
214
+ retryTask = plugin .getServer ().getScheduler ().runTaskTimerAsynchronously (plugin , () -> {
215
+ // Check connection status *first* using the flag
216
+ if (this .isConnected ) {
217
+ ServerPulseLoader .LOGGER .info ("Connection successful, stopping retry task..." );
218
+ stopRetryTask ();
219
+ return ;
220
+ }
221
+
222
+ // Check if plugin got disabled externally
223
+ if (!plugin .isEnabled ()) {
224
+ ServerPulseLoader .LOGGER .warning ("Plugin disabled during retry task execution..." );
225
+ stopRetryTask ();
226
+ return ;
227
+ }
228
+
229
+ // Check retries *before* attempting connection
230
+ if (retryCount >= MAX_RETRIES ) {
231
+ ServerPulseLoader .LOGGER .severe ("Max connection retries (" + MAX_RETRIES + ") reached. Disabling ServerPulse metrics..." );
232
+ stopRetryTask ();
233
+ disconnect (); // Clean up any partial connection
234
+ // Schedule plugin disable on main thread
235
+ plugin .getServer ().getScheduler ().runTask (plugin , () -> plugin .getServer ().getPluginManager ().disablePlugin (plugin ));
236
+ return ;
237
+ }
238
+ retryCount ++;
239
+
240
+ ServerPulseLoader .LOGGER .info ("Retrying InfluxDB connection... Attempt " + retryCount + "/" + MAX_RETRIES );
241
+ connect (); // Note: connect() will handle setting isConnected flag and potentially stopping the task if successful.
242
+ }, RETRY_DELAY_TICKS , RETRY_DELAY_TICKS ); // Start after delay, repeat at delay
66
243
}
67
244
245
+
246
+ /** Stops and nullifies the retry task if it's running. */
247
+ private synchronized void stopRetryTask () {
248
+ if (retryTask != null ) {
249
+ if (!retryTask .isCancelled ()) {
250
+ try {
251
+ retryTask .cancel ();
252
+ } catch (Exception e ) {
253
+ // Ignore potential errors during cancellation
254
+ }
255
+ }
256
+ retryTask = null ;
257
+ }
258
+ }
259
+
260
+
68
261
/**
69
- * Validates the presence and basic correctness of InfluxDB connection details
70
- * in the configuration file.
71
- *
72
- * @return {@code true} if connection data seems valid, {@code false} otherwise.
262
+ * Checks if the essential connection data is present in the config.
263
+ * @return true if data seems present, false otherwise.
73
264
*/
74
265
private boolean checkConnectionData () {
75
266
ConfigurationSection section = customConfig .getConfig ().getConfigurationSection ("metrics.influxdb" );
76
267
if (section == null ) {
268
+ ServerPulseLoader .LOGGER .severe ("Missing 'metrics.influxdb' section in config." );
77
269
return false ;
78
270
}
79
271
String url = section .getString ("url" );
80
272
String bucket = section .getString ("bucket" );
81
273
String org = section .getString ("org" );
82
274
String token = section .getString ("token" );
83
275
84
- return url != null && !url .isEmpty () && bucket != null && !bucket .isEmpty () &&
85
- org != null && !org .isEmpty () && token != null && !token .isEmpty () && !token .equals ("my-token" );
276
+ boolean valid = true ;
277
+ if (url == null || url .isEmpty ()) {
278
+ ServerPulseLoader .LOGGER .severe ("Missing or empty 'metrics.influxdb.url' in config..." );
279
+ valid = false ;
280
+ }
281
+ if (bucket == null || bucket .isEmpty ()) {
282
+ ServerPulseLoader .LOGGER .severe ("Missing or empty 'metrics.influxdb.bucket' in config..." );
283
+ valid = false ;
284
+ }
285
+ if (org == null || org .isEmpty ()) {
286
+ ServerPulseLoader .LOGGER .severe ("Missing or empty 'metrics.influxdb.org' in config..." );
287
+ valid = false ;
288
+ }
289
+ if (token == null || token .isEmpty () || token .equals ("my-token" )) {
290
+ ServerPulseLoader .LOGGER .severe ("Missing, empty, or default 'metrics.influxdb.token' in config..." );
291
+ valid = false ;
292
+ }
293
+ return valid ;
86
294
}
87
- }
295
+ }
0 commit comments