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