View Javadoc
1   /*
2    * Prometheus: Application Framework
3    * Copyright 2012-2026. Tony Washer
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
6    * use this file except in compliance with the License.  You may obtain a copy
7    * of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
14   * License for the specific language governing permissions and limitations under
15   * the License.
16   */
17  package io.github.tonywasher.joceanus.prometheus.database;
18  
19  import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
20  import io.github.tonywasher.joceanus.oceanus.logger.OceanusLogManager;
21  import io.github.tonywasher.joceanus.oceanus.logger.OceanusLogger;
22  import io.github.tonywasher.joceanus.oceanus.profile.OceanusProfile;
23  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataSet;
24  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataSet.PrometheusCryptographyDataType;
25  import io.github.tonywasher.joceanus.prometheus.exc.PrometheusIOException;
26  import io.github.tonywasher.joceanus.tethys.api.thread.TethysUIThreadStatusReport;
27  
28  import java.sql.Connection;
29  import java.sql.DriverManager;
30  import java.sql.PreparedStatement;
31  import java.sql.SQLException;
32  import java.util.ArrayList;
33  import java.util.Iterator;
34  import java.util.List;
35  import java.util.ListIterator;
36  import java.util.Properties;
37  
38  /**
39   * Class that encapsulates a database connection.
40   */
41  public abstract class PrometheusDataStore {
42      /**
43       * Number of update steps per table (INSERT/UPDATE/DELETE).
44       */
45      private static final int NUM_STEPS_PER_TABLE = 3;
46  
47      /**
48       * User property name.
49       */
50      private static final String PROPERTY_USER = "user";
51  
52      /**
53       * Password property name.
54       */
55      private static final String PROPERTY_PASS = "password";
56  
57      /**
58       * Instance property name.
59       */
60      private static final String PROPERTY_INSTANCE = "instance";
61  
62      /**
63       * Encrypt property name.
64       */
65      private static final String PROPERTY_ENCRYPT = "encrypt";
66  
67      /**
68       * Logger.
69       */
70      private static final OceanusLogger LOGGER = OceanusLogManager.getLogger(PrometheusDataStore.class);
71  
72      /**
73       * Database connection.
74       */
75      private Connection theConn;
76  
77      /**
78       * Database name.
79       */
80      private String theDatabase;
81  
82      /**
83       * Batch Size.
84       */
85      private final Integer theBatchSize;
86  
87      /**
88       * Database Driver.
89       */
90      private final PrometheusJDBCDriver theDriver;
91  
92      /**
93       * List of Database tables.
94       */
95      private final List<PrometheusTableDataItem<?>> theTables;
96  
97      /**
98       * Construct a new Database class.
99       *
100      * @param pDatabase the database
101      * @param pConfig   the config
102      * @throws OceanusException on error
103      */
104     protected PrometheusDataStore(final String pDatabase,
105                                   final PrometheusDBConfig pConfig) throws OceanusException {
106         /* Create the connection */
107         try {
108             /* Access the batch size */
109             theBatchSize = pConfig.getBatchSize();
110 
111             /* Access the JDBC Driver */
112             theDriver = pConfig.getDriver();
113 
114             /* Store the name */
115             theDatabase = pDatabase;
116 
117             /* Obtain the connection */
118             final String myConnString = theDriver.getConnectionString(pDatabase, pConfig.getServer(), pConfig.getPort());
119 
120             /* Create the properties and record user */
121             final Properties myProperties = new Properties();
122             final String myUser = pConfig.getUser();
123             final char[] myPass = pConfig.getPassword();
124             myProperties.setProperty(PROPERTY_USER, myUser);
125             myProperties.setProperty(PROPERTY_PASS, new String(myPass));
126 
127             /* If we are using instance */
128             if (theDriver.useInstance()) {
129                 final String myInstance = pConfig.getInstance();
130                 myProperties.setProperty(PROPERTY_INSTANCE, myInstance);
131                 myProperties.setProperty(PROPERTY_ENCRYPT, "false");
132             }
133 
134             /* Connect using properties */
135             theConn = DriverManager.getConnection(myConnString, myProperties);
136 
137             /* Connect to the correct database */
138             theConn.setCatalog(pDatabase);
139 
140             /* Switch off autoCommit */
141             theConn.setAutoCommit(false);
142 
143             /* handle exceptions */
144         } catch (SQLException e) {
145             throw new PrometheusIOException("Failed to load driver", e);
146         }
147 
148         /* Create table list and add the tables to the list */
149         theTables = new ArrayList<>();
150 
151         /* Loop through the tables */
152         for (PrometheusCryptographyDataType myType : PrometheusCryptographyDataType.values()) {
153             /* Create the sheet */
154             theTables.add(newTable(myType));
155         }
156     }
157 
158     /**
159      * Construct a new Database class.
160      *
161      * @param pConfig the config
162      * @throws OceanusException on error
163      */
164     protected PrometheusDataStore(final PrometheusDBConfig pConfig) throws OceanusException {
165         /* Create the connection */
166         try {
167             /* Access the batch size */
168             theBatchSize = pConfig.getBatchSize();
169 
170             /* Access the JDBC Driver */
171             theDriver = pConfig.getDriver();
172 
173             /* Obtain the connection */
174             final String myConnString = theDriver.getConnectionString(pConfig.getServer(), pConfig.getPort());
175 
176             /* Create the properties and record user */
177             final Properties myProperties = new Properties();
178             final String myUser = pConfig.getUser();
179             final char[] myPass = pConfig.getPassword();
180             myProperties.setProperty(PROPERTY_USER, myUser);
181             myProperties.setProperty(PROPERTY_PASS, new String(myPass));
182 
183             /* If we are using instance */
184             if (theDriver.useInstance()) {
185                 final String myInstance = pConfig.getInstance();
186                 myProperties.setProperty(PROPERTY_INSTANCE, myInstance);
187                 myProperties.setProperty(PROPERTY_ENCRYPT, "false");
188             }
189 
190             /* Connect using properties */
191             theConn = DriverManager.getConnection(myConnString, myProperties);
192 
193             /* Switch off autoCommit */
194             theConn.setAutoCommit(false);
195 
196             /* handle exceptions */
197         } catch (SQLException e) {
198             throw new PrometheusIOException("Failed to load driver", e);
199         }
200 
201         /* Create table list and add the tables to the list */
202         theTables = new ArrayList<>();
203 
204         /* Loop through the tables */
205         for (PrometheusCryptographyDataType myType : PrometheusCryptographyDataType.values()) {
206             /* Create the sheet */
207             theTables.add(newTable(myType));
208         }
209     }
210 
211     /**
212      * Obtain the database name.
213      *
214      * @return the name
215      */
216     public String getName() {
217         return theDatabase;
218     }
219 
220     /**
221      * Execute the statement outside a transaction.
222      *
223      * @param pStatement the statement
224      * @throws OceanusException on error
225      */
226     void executeStatement(final String pStatement) throws OceanusException {
227         /* Protect the statement and execute without commit */
228         try (PreparedStatement myStmt = theConn.prepareStatement(pStatement)) {
229             theConn.setAutoCommit(true);
230             myStmt.execute();
231             theConn.setAutoCommit(false);
232 
233         } catch (SQLException e) {
234             throw new PrometheusIOException("Failed to execute statement", e);
235         }
236     }
237 
238     /**
239      * Create new sheet of required type.
240      *
241      * @param pListType the list type
242      * @return the new sheet
243      */
244     private PrometheusTableDataItem<?> newTable(final PrometheusCryptographyDataType pListType) {
245         /* Switch on list Type */
246         switch (pListType) {
247             case CONTROLDATA:
248                 return new PrometheusTableControlData(this);
249             case CONTROLKEY:
250                 return new PrometheusTableControlKeys(this);
251             case CONTROLKEYSET:
252                 return new PrometheusTableControlKeySet(this);
253             case DATAKEYSET:
254                 return new PrometheusTableDataKeySet(this);
255             default:
256                 throw new IllegalArgumentException(pListType.toString());
257         }
258     }
259 
260     /**
261      * Obtain the Driver.
262      *
263      * @return the driver
264      */
265     protected PrometheusJDBCDriver getDriver() {
266         return theDriver;
267     }
268 
269     /**
270      * Access the connection.
271      *
272      * @return the connection
273      */
274     protected Connection getConn() {
275         return theConn;
276     }
277 
278     /**
279      * Add a table.
280      *
281      * @param pTable the Table to add
282      */
283     protected void addTable(final PrometheusTableDataItem<?> pTable) {
284         pTable.getDefinition().resolveReferences(theTables);
285         theTables.add(pTable);
286     }
287 
288     /**
289      * Close the connection to the database rolling back any outstanding transaction.
290      */
291     public void close() {
292         /* Ignore if no connection */
293         if (theConn == null) {
294             return;
295         }
296 
297         /* Protect against exceptions */
298         try {
299             /* Roll-back any outstanding transaction */
300             if (!theConn.getAutoCommit()) {
301                 theConn.rollback();
302             }
303 
304             /* Loop through the tables */
305             for (PrometheusTableDataItem<?> myTable : theTables) {
306                 /* Close the Statement */
307                 myTable.closeStmt();
308             }
309 
310             /* Close the connection */
311             theConn.close();
312             theConn = null;
313 
314             /* Discard Exceptions */
315         } catch (SQLException e) {
316             LOGGER.error("Failed to close database connection", e);
317             theConn = null;
318         }
319     }
320 
321     /**
322      * Load data from the database.
323      *
324      * @param pReport the report
325      * @param pData   the new DataSet
326      * @throws OceanusException on error
327      */
328     public void loadDatabase(final TethysUIThreadStatusReport pReport,
329                              final PrometheusDataSet pData) throws OceanusException {
330         /* Initialise task */
331         pReport.initTask("loadDatabase");
332         pReport.setNumStages(theTables.size());
333 
334         /* Obtain the active profile */
335         OceanusProfile myTask = pReport.getActiveTask();
336         myTask = myTask.startTask("loadDatabase");
337 
338         /* Loop through the tables */
339         for (PrometheusTableDataItem<?> myTable : theTables) {
340             /* Note the new step */
341             myTask.startTask(myTable.getTableName());
342 
343             /* Load the items */
344             myTable.loadItems(pReport, pData);
345         }
346 
347         /* Complete the task */
348         myTask.end();
349     }
350 
351     /**
352      * Update data into database.
353      *
354      * @param pReport the report
355      * @param pData   the data
356      * @throws OceanusException on error
357      */
358     public void updateDatabase(final TethysUIThreadStatusReport pReport,
359                                final PrometheusDataSet pData) throws OceanusException {
360         /* Set the number of stages */
361         final PrometheusBatchControl myBatch = new PrometheusBatchControl(theBatchSize);
362         pReport.setNumStages(NUM_STEPS_PER_TABLE * theTables.size());
363 
364         /* Obtain the active profile */
365         OceanusProfile myTask = pReport.getActiveTask();
366         myTask = myTask.startTask("updateDatabase");
367 
368         /* Loop through the tables */
369         OceanusProfile myStage = myTask.startTask("insertData");
370         final Iterator<PrometheusTableDataItem<?>> myIterator = theTables.iterator();
371         while (myIterator.hasNext()) {
372             final PrometheusTableDataItem<?> myTable = myIterator.next();
373 
374             /* Note the new step */
375             myStage.startTask(myTable.getTableName());
376 
377             /* insert the items */
378             myTable.insertItems(pReport, pData, myBatch);
379         }
380 
381         /* Loop through the tables */
382         myStage = myTask.startTask("updateData");
383         final ListIterator<PrometheusTableDataItem<?>> myListIterator = theTables.listIterator();
384         while (myListIterator.hasNext()) {
385             final PrometheusTableDataItem<?> myTable = myListIterator.next();
386 
387             /* Note the new step */
388             myStage.startTask(myTable.getTableName());
389 
390             /* Load the items */
391             myTable.updateItems(pReport, myBatch);
392         }
393 
394         /* Loop through the tables in reverse order */
395         myStage = myTask.startTask("deleteData");
396         while (myListIterator.hasPrevious()) {
397             final PrometheusTableDataItem<?> myTable = myListIterator.previous();
398 
399             /* Note the new step */
400             myStage.startTask(myTable.getTableName());
401 
402             /* Delete items from the table */
403             myTable.deleteItems(pReport, myBatch);
404         }
405 
406         /* If we have active work in the batch */
407         if (myBatch.isActive()) {
408             /* Commit the database */
409             try {
410                 theConn.commit();
411             } catch (SQLException e) {
412                 close();
413                 throw new PrometheusIOException("Failed to commit transaction", e);
414             }
415 
416             /* Commit the batch */
417             myBatch.commitItems();
418         }
419 
420         /* Complete the task */
421         myTask.end();
422     }
423 
424     /**
425      * Create database.
426      *
427      * @param pReport   the report
428      * @param pDatabase the database to create
429      * @throws OceanusException on error
430      */
431     public void createDatabase(final TethysUIThreadStatusReport pReport,
432                                final String pDatabase) throws OceanusException {
433         /* Set the number of stages */
434         pReport.setNumStages(2);
435 
436         /* Obtain the active profile */
437         OceanusProfile myTask = pReport.getActiveTask();
438         myTask = myTask.startTask("dropDatabase");
439         executeStatement("DROP DATABASE IF EXISTS " + pDatabase);
440 
441         /* Create database */
442         myTask = myTask.startTask("createDatabase");
443         executeStatement("CREATE DATABASE " + pDatabase);
444 
445         /* Complete the task */
446         myTask.end();
447     }
448 
449     /**
450      * Create tables.
451      *
452      * @param pReport the report
453      * @throws OceanusException on error
454      */
455     public void createTables(final TethysUIThreadStatusReport pReport) throws OceanusException {
456         /* Set the number of stages */
457         pReport.setNumStages(2);
458 
459         /* Drop any existing tables */
460         dropTables(pReport);
461 
462         /* Obtain the active profile */
463         OceanusProfile myTask = pReport.getActiveTask();
464         myTask = myTask.startTask("createTables");
465 
466         /* Loop through the tables */
467         final Iterator<PrometheusTableDataItem<?>> myIterator = theTables.iterator();
468         while (myIterator.hasNext()) {
469             final PrometheusTableDataItem<?> myTable = myIterator.next();
470 
471             /* Check for cancellation */
472             pReport.checkForCancellation();
473 
474             /* Note the new step */
475             myTask.startTask(myTable.getTableName());
476 
477             /* Create the table */
478             myTable.createTable();
479         }
480 
481         /* Complete the task */
482         myTask.end();
483     }
484 
485     /**
486      * Drop tables.
487      *
488      * @param pReport the report
489      * @throws OceanusException on error
490      */
491     private void dropTables(final TethysUIThreadStatusReport pReport) throws OceanusException {
492         /* Obtain the active profile */
493         OceanusProfile myTask = pReport.getActiveTask();
494         myTask = myTask.startTask("dropTables");
495 
496         /* Loop through the tables in reverse order */
497         final ListIterator<PrometheusTableDataItem<?>> myIterator = theTables.listIterator(theTables.size());
498         while (myIterator.hasPrevious()) {
499             final PrometheusTableDataItem<?> myTable = myIterator.previous();
500 
501             /* Check for cancellation */
502             pReport.checkForCancellation();
503 
504             /* Note the new step */
505             myTask.startTask(myTable.getTableName());
506 
507             /* Drop the table */
508             myTable.dropTable();
509         }
510 
511         /* Complete the task */
512         myTask.end();
513     }
514 
515     /**
516      * Purge tables.
517      *
518      * @param pReport the report
519      * @throws OceanusException on error
520      */
521     public void purgeTables(final TethysUIThreadStatusReport pReport) throws OceanusException {
522         /* Set the number of stages */
523         pReport.setNumStages(1);
524 
525         /* Obtain the active profile */
526         OceanusProfile myTask = pReport.getActiveTask();
527         myTask = myTask.startTask("purgeTables");
528 
529         /* Loop through the tables in reverse order */
530         final ListIterator<PrometheusTableDataItem<?>> myIterator = theTables.listIterator(theTables.size());
531         while (myIterator.hasPrevious()) {
532             final PrometheusTableDataItem<?> myTable = myIterator.previous();
533 
534             /* Check for cancellation */
535             pReport.checkForCancellation();
536 
537             /* Note the new step */
538             myTask.startTask(myTable.getTableName());
539 
540             /* Purge the table */
541             myTable.purgeTable();
542         }
543 
544         /* Complete the task */
545         myTask.end();
546     }
547 }