1 /*-
2  * Copyright (c) 2001, 2020 Oracle and/or its affiliates.  All rights reserved.
3  *
4  * See the file LICENSE for license information.
5  *
6  * $Id$
7  */
8 package db_gui.datapage;
9 
10 import com.sleepycat.db.DatabaseEntry;
11 import com.sleepycat.db.DatabaseException;
12 import com.sleepycat.db.DeadlockException;
13 import com.sleepycat.db.LockMode;
14 import com.sleepycat.db.OperationStatus;
15 
16 import java.net.URL;
17 import java.nio.BufferUnderflowException;
18 import java.nio.ByteBuffer;
19 import java.nio.charset.StandardCharsets;
20 import java.util.ResourceBundle;
21 import java.util.logging.Level;
22 import java.util.logging.Logger;
23 
24 import javafx.event.ActionEvent;
25 import javafx.fxml.FXML;
26 import javafx.scene.control.ComboBox;
27 import javafx.scene.control.TextField;
28 import db_gui.BDBInitializable;
29 
30 /**
31  * BDBDataInitializeable is the parent class of all the controllers of the
32  * Data Access Page.  It implements operations that are part of all or most of
33  * each of the data access pages for the different database access methods, such
34  * as translating the the Get and Put data TextFields.
35  */
36 public abstract class BDBDataInitializable extends BDBInitializable {
37     @FXML
38     protected TextField GetDataTextField;
39     @FXML
40     protected ComboBox<String> GetDataComboBox;
41     @FXML
42     protected TextField PutDataTextField;
43     @FXML
44     protected ComboBox<String> PutDataComboBox;
45     protected DatabaseEntry currentGetData;
46     final static protected String[] dataFormats = {"ASCII",
47             "UTF-8", "UTF-16", "Integer 32", "Float 32", "Integer 64",
48             "Float 64"};
49 
50     /**
51      * Initializes the controller class.
52      */
53     @Override
initialize(URL location, ResourceBundle resources)54     public void initialize(URL location, ResourceBundle resources) {
55         GetDataComboBox.getItems().addAll(dataFormats);
56         GetDataComboBox.getSelectionModel().selectFirst();
57         // The Queue Data Access page does not have a PutDataComboBox.
58         if (PutDataComboBox != null) {
59             PutDataComboBox.getItems().addAll(dataFormats);
60             PutDataComboBox.getSelectionModel().selectFirst();
61         }
62     }
63 
64     /**
65      * Clears the fields when the Clear button is pressed.
66      */
67     @Override
clearAllFields()68     public void clearAllFields() {
69         GetDataTextField.clear();
70         GetDataComboBox.getSelectionModel().selectFirst();
71         if (PutDataTextField != null)
72             PutDataTextField.clear();
73         if (PutDataComboBox != null)
74             PutDataComboBox.getSelectionModel().selectFirst();
75         currentGetData = null;
76         bdbState.closeCursor();
77     }
78 
79     /**
80      * Get the Put Key DatabaseEntry from the inheriting class.
81      *
82      * @return The Put Key DatabaseEntry.
83      */
getPutKeyDatabaseEntry()84     public abstract DatabaseEntry getPutKeyDatabaseEntry();
85 
86     /**
87      * Display the Put Key.
88      *
89      * @param key - The DatabaseEntry of the Put Key.
90      */
setPutKeyDatabaseEntry(DatabaseEntry key)91     public abstract void setPutKeyDatabaseEntry(DatabaseEntry key);
92 
93     /**
94      * Get the Get Key DatabaseEntry from the inheriting class.
95      *
96      * @return The Get Key DatabaseEntry.
97      */
getGetKeyDatabaseEntry()98     public abstract DatabaseEntry getGetKeyDatabaseEntry();
99 
100     /**
101      * Display the Get Key.
102      *
103      * @param key - The DatabaseEntry of the Get Key.
104      */
setGetKeyDatabaseEntry(DatabaseEntry key)105     public abstract void setGetKeyDatabaseEntry(DatabaseEntry key);
106 
107     /**
108      * Whether the Get Key TextField has text in it or not.
109      *
110      * @return True if the Get Key TextField contains text, false otherwise.
111      */
getGetKeySet()112     public abstract boolean getGetKeySet();
113 
114     /**
115      * Whether to append or put new records into the database.
116      *
117      * @return True if to append new records, false if to put.
118      */
getAppendRecords()119     public abstract boolean getAppendRecords();
120 
121 
122     /**
123      * Returns the given data string as a DatabaseEntry.  The string
124      * is translated into bytes based on what type is passed in.  Null
125      * is returned if there is a parsing error.
126      *
127      * @param data - The data to be made into a DatabaseEntry.
128      * @param type - How to translate the data String into binary data.
129      * @return - The DatabaseEntry created from the data.
130      */
getDatabaseEntry(String data, String type)131     protected DatabaseEntry getDatabaseEntry(String data, String type) {
132         if (data == null)
133             return null;
134         if (type == null)
135             return new DatabaseEntry(
136                     data.getBytes(StandardCharsets.US_ASCII));
137         switch (type) {
138             case ("ASCII"):
139                 return new DatabaseEntry(
140                         data.getBytes(StandardCharsets.US_ASCII));
141             case ("UTF-8"):
142                 return new DatabaseEntry(
143                         data.getBytes(StandardCharsets.UTF_8));
144             case ("UTF-16"):
145                 return new DatabaseEntry(
146                         data.getBytes(StandardCharsets.UTF_16));
147             case ("Integer 32"):
148                 int ivalue;
149                 try {
150                     ivalue = Integer.parseInt(data);
151                 } catch (NumberFormatException ex) {
152                     bdbState.printFeedback(
153                             "Error, cannot parse value \""
154                                     + data + "\" into a 32-bit integer.");
155                     bdbState.printFeedback("Pleae enter another value.");
156                     return null;
157                 }
158                 ByteBuffer bbuf = ByteBuffer.allocate(Integer.BYTES);
159                 bbuf.putInt(ivalue);
160                 return new DatabaseEntry(bbuf.array());
161             case ("Float 32"):
162                 float fvalue;
163                 try {
164                     fvalue = Float.parseFloat(data);
165                 } catch (NumberFormatException ex) {
166                     bdbState.printFeedback(
167                             "Error, cannot parse value \""
168                                     + data + "\" into a 32-bit float.");
169                     bdbState.printFeedback("Pleae enter another value.");
170                     return null;
171                 }
172                 bbuf = ByteBuffer.allocate(Float.BYTES);
173                 bbuf.putFloat(fvalue);
174                 return new DatabaseEntry(bbuf.array());
175             case ("Integer 64"):
176                 long lvalue;
177                 try {
178                     lvalue = Long.parseLong(data);
179                 } catch (NumberFormatException ex) {
180                     bdbState.printFeedback(
181                             "Error, cannot parse value \""
182                                     + data + "\" into a 64-bit integer.");
183                     bdbState.printFeedback("Pleae enter another value.");
184                     return null;
185                 }
186                 bbuf = ByteBuffer.allocate(Long.BYTES);
187                 bbuf.putLong(lvalue);
188                 return new DatabaseEntry(bbuf.array());
189             case ("Float 64"):
190                 double dvalue;
191                 try {
192                     dvalue = Double.parseDouble(data);
193                 } catch (NumberFormatException ex) {
194                     bdbState.printFeedback(
195                             "Error, cannot parse value \""
196                                     + data + "\" into a 64-bit float.");
197                     bdbState.printFeedback("Pleae enter another value.");
198                     return null;
199                 }
200                 bbuf = ByteBuffer.allocate(Double.BYTES);
201                 bbuf.putDouble(dvalue);
202                 return new DatabaseEntry(bbuf.array());
203             default:
204                 return null;
205         }
206     }
207 
208     /**
209      * Takes a DatabaseEntry and type and returns a string that displays
210      * the data based on the type.
211      *
212      * @param data - The DatabaseEntry to translate into a String.
213      * @param type - How to translate the binary DatabaseEntry data into a
214      * String.
215      * @return The String into which the data was translated.
216      */
getDatabaseEntryString(DatabaseEntry data, String type)217     protected String getDatabaseEntryString(DatabaseEntry data, String type) {
218         if (data == null
219                 || data.getData() == null || data.getData().length == 0)
220             return "";
221         switch (type) {
222             case ("ASCII"):
223                 return new String(data.getData(), StandardCharsets.US_ASCII);
224             case ("UTF-8"):
225                 return new String(data.getData(), StandardCharsets.UTF_8);
226             case ("UTF-16"):
227                 return new String(data.getData(), StandardCharsets.UTF_16);
228             case ("Integer 32"):
229                 ByteBuffer bbuf = ByteBuffer.wrap(data.getData());
230                 int ivalue;
231                 try {
232                     ivalue = bbuf.getInt();
233                 } catch (BufferUnderflowException ex) {
234                     bdbState.printFeedback(
235                             "Error, cannot display value as an integer.");
236                     return null;
237                 }
238                 return Integer.toString(ivalue);
239             case ("Float 32"):
240                 float fvalue;
241                 bbuf = ByteBuffer.wrap(data.getData());
242                 try {
243                     fvalue = bbuf.getFloat();
244                 } catch (BufferUnderflowException ex) {
245                     bdbState.printFeedback(
246                             "Error, cannot display value as a float.");
247                     return null;
248                 }
249                 return Float.toString(fvalue);
250             case ("Integer 64"):
251                 long lvalue;
252                 bbuf = ByteBuffer.wrap(data.getData());
253                 try {
254                     lvalue = bbuf.getLong();
255                 } catch (BufferUnderflowException ex) {
256                     bdbState.printFeedback(
257                             "Error, cannot display value as a 64-bit integer.");
258                     return null;
259                 }
260                 return Long.toString(lvalue);
261             case ("Float 64"):
262                 double dvalue;
263                 bbuf = ByteBuffer.wrap(data.getData());
264                 try {
265                     dvalue = bbuf.getDouble();
266                 } catch (BufferUnderflowException ex) {
267                     bdbState.printFeedback(
268                             "Error, cannot display value as a 64-bit float.");
269                     return null;
270                 }
271                 return Double.toString(dvalue);
272             default:
273                 return null;
274         }
275     }
276 
277     /**
278      * Changes how the data in the text field is displayed.
279      *
280      * @param dbEntry - The DatabaseEntry containing the binary data to be
281      * displayed.
282      * @param textField - The TextField in which to display the data.
283      * @param comboBox - The ComboBox containing how to translate the binary
284      * data into a String.
285      */
changeDataDisplay(DatabaseEntry dbEntry, TextField textField, ComboBox<String> comboBox)286     protected void changeDataDisplay(DatabaseEntry dbEntry,
287             TextField textField, ComboBox<String> comboBox) {
288         // Do nothing if the field is empty.
289         if (dbEntry == null || dbEntry.getSize() == 0)
290             return;
291         String type = comboBox.getSelectionModel().getSelectedItem();
292         if (type == null || type.length() == 0)
293             return;
294         String text = getDatabaseEntryString(dbEntry, type);
295         if (text != null && text.length() != 0)
296             textField.setText(text);
297     }
298 
299     /**
300      * Changes how the data in the GetDataTextField is displayed when the type
301      * selected in the GetDataComboBox is changed.
302      *
303      * @param event - Unused.
304      */
305     @FXML
handleGetDataComboBox(ActionEvent event)306     protected void handleGetDataComboBox(ActionEvent event) {
307         changeDataDisplay(currentGetData, GetDataTextField, GetDataComboBox);
308     }
309 
310     /**
311      * Handles the Put button on the Data Access page.  Either puts or appends
312      * the entered key and data depending on the access method of the database.
313      * Closes the current cursor if it is opened.
314      *
315      * @param event - Unused.
316      */
317     @FXML
handlePutButton(ActionEvent event)318     protected void handlePutButton(ActionEvent event) {
319         String dataString;
320         DatabaseEntry key, data;
321 
322         dataString = PutDataTextField.getText();
323         key = getPutKeyDatabaseEntry();
324         if (key == null)
325             return;
326         data = getDatabaseEntry(dataString,
327                 PutDataComboBox.getSelectionModel().getSelectedItem());
328         /* data are only null if there was a parsing error. */
329         if (data == null)
330             return;
331 
332         try {
333             /*
334              * Close the current cursor if it exists, otherwise it could cause
335              * the thread to hang.
336              */
337             bdbState.closeCursor();
338             if (getAppendRecords())
339                 bdbState.getDb().append(bdbState.getTxn(), key, data);
340             else
341                 bdbState.getDb().put(bdbState.getTxn(), key, data);
342             // Clear the test fields after a successful insert.
343             setPutKeyDatabaseEntry(null);
344             PutDataTextField.clear();
345             GetDataTextField.clear();
346             currentGetData = null;
347             setGetKeyDatabaseEntry(null);
348             bdbState.printFeedback("Inserted record sucessful.");
349         } catch (DeadlockException dex) {
350             handleDeadlockException();
351         } catch (DatabaseException ex) {
352             Logger.getLogger(
353                     BtreeHashController.class.getName()).log(
354                     Level.SEVERE, null, ex);
355             bdbState.printFeedback(
356                     "Error putting values into the database: "
357                             + ex.getMessage());
358         }
359     }
360 
361     /**
362      * Handles the Get button on the Data Access page.  Closes the current
363      * cursor if it exists.
364      *
365      * @param event - Unused.
366      */
367     @FXML
handleGetButton(ActionEvent event)368     protected void handleGetButton(ActionEvent event) {
369         String dataString;
370         DatabaseEntry key, data;
371 
372         key = getGetKeyDatabaseEntry();
373         if (key == null)
374             return;
375         data = new DatabaseEntry();
376 
377         try {
378             /*
379              * Close the current cursor if it exists, otherwise it could cause
380              * the thread to hang.
381              */
382             bdbState.closeCursor();
383             OperationStatus op = bdbState.getDb().get(
384                     bdbState.getTxn(), key, data, LockMode.DEFAULT);
385             if (op == OperationStatus.NOTFOUND)
386                 bdbState.printFeedback("Database does not contain that key.");
387             else {
388                 bdbState.printFeedback(
389                         "Success getting record from the database.");
390                 setGetKeyDatabaseEntry(key);
391                 currentGetData = data;
392                 dataString = getDatabaseEntryString(data,
393                         GetDataComboBox.getSelectionModel().getSelectedItem());
394                 GetDataTextField.setText(dataString);
395             }
396         } catch (DeadlockException dex) {
397             handleDeadlockException();
398         } catch (DatabaseException ex) {
399             Logger.getLogger(
400                     BtreeHashController.class.getName()).log(
401                     Level.SEVERE, null, ex);
402             bdbState.printFeedback(
403                     "Error getting record from the database: "
404                             + ex.getMessage());
405         }
406     }
407 
408     /**
409      * Handles the Next button on the data access page.  If the cursor exists
410      * then it calls the Cursor.next function, if it does not exist then it
411      * opens a new Cursor and positions it at the last know position before
412      * calling next, or if the is no known last position it calls getFirst.
413      *
414      * @param event - Unused.
415      */
416     @FXML
handleNextButton(ActionEvent event)417     protected void handleNextButton(ActionEvent event) {
418         String dataString;
419         DatabaseEntry key, data;
420         boolean getFirst = false;
421 
422         if (bdbState.getCursor() == null) {
423             try {
424                 bdbState.openCursor();
425             } catch (DatabaseException ex) {
426                 Logger.getLogger(
427                         BtreeHashController.class.getName()).log(
428                         Level.SEVERE, null, ex);
429                 bdbState.printFeedback(
430                         "Error opening database cursor: " + ex.getMessage());
431                 return;
432             }
433             getFirst = true;
434 
435             /*if (getGetKeySet()) {
436                 key = getGetKeyDatabaseEntry();
437                 if (key == null)
438                     key = new DatabaseEntry();
439                 data = new DatabaseEntry();
440 
441                 try {
442                     OperationStatus op =
443                         bdbState.getCursor().getSearchKey(
444                             key, data, LockMode.DEFAULT);
445                     if (op == OperationStatus.NOTFOUND) {
446                         bdbState.printFeedback(
447                                 "Unable to find current key, getting " +
448                                         "the first record in the database.");
449                         getFirst = true;
450                     }
451                 } catch (DeadlockException dex) {
452                     handleDeadlockException();
453                     bdbState.printFeedback("Please retry Next operation.");
454                     return;
455                 } catch (DatabaseException ex) {
456                     Logger.getLogger(
457                             BtreeHashController.class.getName()).log(
458                                     Level.SEVERE, null, ex);
459                     bdbState.printFeedback(
460                             "Error getting next record: " + ex.getMessage());
461                     return;
462                 }
463             } else
464                 getFirst = true;*/
465         }
466         key = new DatabaseEntry();
467         data = new DatabaseEntry();
468         try {
469             OperationStatus op;
470             if (getFirst)
471                 op = bdbState.getCursor().getFirst(key, data, LockMode.DEFAULT);
472             else
473                 op = bdbState.getCursor().getNext(key, data, LockMode.DEFAULT);
474             if (op == OperationStatus.NOTFOUND) {
475                 bdbState.printFeedback(
476                         "End of the database has been reached, "
477                                 + "there is no next record.");
478                 setGetKeyDatabaseEntry(null);
479                 GetDataTextField.clear();
480             } else {
481                 bdbState.printFeedback(
482                         "Success getting the next record from the database.");
483                 currentGetData = data;
484                 setGetKeyDatabaseEntry(key);
485                 dataString = getDatabaseEntryString(data,
486                         GetDataComboBox.getSelectionModel().getSelectedItem());
487                 GetDataTextField.setText(dataString);
488             }
489         } catch (DatabaseException ex) {
490             Logger.getLogger(
491                     BtreeHashController.class.getName()).log(
492                     Level.SEVERE, null, ex);
493             bdbState.printFeedback(
494                     "Error getting record from the database: "
495                             + ex.getMessage());
496         }
497     }
498 
499     /**
500      * Handles the Previous button on the data access page.  If the cursor
501      * exists then it calls the Cursor.previous function, if it does not exist
502      * then it opens a new Cursor and positions it at the last know position
503      * before calling previous, or if the is no known last position it calls
504      * getLast.
505      *
506      * @param event - Unused.
507      */
508     @FXML
handlePreviousButton(ActionEvent event)509     protected void handlePreviousButton(ActionEvent event) {
510         String dataString;
511         DatabaseEntry key, data;
512         boolean getLast = false;
513 
514         if (bdbState.getCursor() == null) {
515             try {
516                 bdbState.openCursor();
517             } catch (DatabaseException ex) {
518                 Logger.getLogger(
519                         BtreeHashController.class.getName()).log(
520                         Level.SEVERE, null, ex);
521                 bdbState.printFeedback(
522                         "Error opening database cursor: " + ex.getMessage());
523                 return;
524             }
525             getLast = true;
526             /*if (getGetKeySet()) {
527                 key = getGetKeyDatabaseEntry();
528                 if (key == null)
529                     key = new DatabaseEntry();
530                 data = new DatabaseEntry();
531 
532                 try {
533                     OperationStatus op =
534                         bdbState.getCursor().getSearchKey(
535                             key, data, LockMode.DEFAULT);
536                     if (op == OperationStatus.NOTFOUND) {
537                         bdbState.printFeedback(
538                                 "Unable to find current key, getting " +
539                                         "the last record in the database.");
540                         getLast = true;
541                     }
542                 } catch (DeadlockException dex) {
543                     handleDeadlockException();
544                     bdbState.printFeedback("Please retry Previous operation.");
545                     return;
546                 } catch (DatabaseException ex) {
547                     Logger.getLogger(
548                             BtreeHashController.class.getName()).log(
549                                     Level.SEVERE, null, ex);
550                     bdbState.printFeedback(
551                             "Error getting previous record: "
552                                     + ex.getMessage());
553                     return;
554                 }
555             } else
556                 getLast = true;*/
557         }
558         key = new DatabaseEntry();
559         data = new DatabaseEntry();
560         try {
561             OperationStatus op;
562             if (getLast)
563                 op = bdbState.getCursor().getLast(key, data, LockMode.DEFAULT);
564             else
565                 op = bdbState.getCursor().getPrev(key, data, LockMode.DEFAULT);
566             if (op == OperationStatus.NOTFOUND) {
567                 bdbState.printFeedback(
568                         "Beginning of the database has been reached, "
569                                 + "there is no previous record.");
570                 setGetKeyDatabaseEntry(null);
571                 GetDataTextField.clear();
572             } else {
573                 currentGetData = data;
574                 bdbState.printFeedback(
575                         "Success getting the previous record from the database.");
576                 setGetKeyDatabaseEntry(key);
577                 dataString = getDatabaseEntryString(data,
578                         GetDataComboBox.getSelectionModel().getSelectedItem());
579                 GetDataTextField.setText(dataString);
580             }
581         } catch (DatabaseException ex) {
582             Logger.getLogger(
583                     BtreeHashController.class.getName()).log(
584                     Level.SEVERE, null, ex);
585             bdbState.printFeedback(
586                     "Error getting record from the database: "
587                             + ex.getMessage());
588         }
589     }
590 
591     /**
592      * Handles the Delete button.  Deletes the current entry that was either
593      * entered by the user, or set by the cursor.
594      *
595      * @param event - Unused.
596      */
597     @FXML
handleDeleteButton(ActionEvent event)598     protected void handleDeleteButton(ActionEvent event) {
599         DatabaseEntry key;
600 
601         if (!getGetKeySet()) {
602             bdbState.printFeedback(
603                     "Error, please enter a Key value or start a cursor.");
604             return;
605         }
606         key = getGetKeyDatabaseEntry();
607         /* key is only null if there was a parsing error. */
608         if (key == null)
609             return;
610 
611         try {
612             bdbState.closeCursor();
613             OperationStatus op =
614                     bdbState.getDb().delete(bdbState.getTxn(), key);
615             if (op == OperationStatus.SUCCESS) {
616                 bdbState.printFeedback("Success deleting record.");
617             } else if (op == OperationStatus.NOTFOUND) {
618                 bdbState.printFeedback(
619                         "No such record exists in the database.");
620             }
621             setGetKeyDatabaseEntry(null);
622             GetDataTextField.clear();
623         } catch (DeadlockException dex) {
624             handleDeadlockException();
625         } catch (DatabaseException ex) {
626             Logger.getLogger(
627                     BtreeHashController.class.getName()).log(
628                     Level.SEVERE, null, ex);
629             bdbState.printFeedback(
630                     "Error deleting record from database: "
631                             + ex.getMessage());
632         }
633     }
634 }
635