001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2008-2010 Sun Microsystems, Inc. 025 * Portions Copyright 2011-2015 ForgeRock AS 026 */ 027 028package org.opends.guitools.controlpanel.ui; 029 030import static org.opends.messages.AdminToolMessages.*; 031import static org.opends.messages.ToolMessages.*; 032import static org.opends.server.util.ServerConstants.*; 033 034import static com.forgerock.opendj.util.OperatingSystem.*; 035 036import java.awt.Component; 037import java.awt.Dimension; 038import java.awt.GridBagConstraints; 039import java.awt.GridBagLayout; 040import java.awt.event.ActionEvent; 041import java.awt.event.ActionListener; 042import java.io.File; 043import java.util.ArrayList; 044import java.util.GregorianCalendar; 045import java.util.LinkedHashSet; 046import java.util.List; 047import java.util.Set; 048 049import javax.swing.Box; 050import javax.swing.JButton; 051import javax.swing.JLabel; 052import javax.swing.JPanel; 053import javax.swing.JScrollPane; 054import javax.swing.JTable; 055import javax.swing.JTextField; 056import javax.swing.ListSelectionModel; 057import javax.swing.SwingConstants; 058import javax.swing.SwingUtilities; 059import javax.swing.event.ListSelectionEvent; 060import javax.swing.event.ListSelectionListener; 061import javax.swing.table.TableColumn; 062 063import org.forgerock.i18n.LocalizableMessage; 064import org.forgerock.i18n.slf4j.LocalizedLogger; 065import org.opends.guitools.controlpanel.datamodel.BackupDescriptor; 066import org.opends.guitools.controlpanel.datamodel.BackupTableModel; 067import org.opends.guitools.controlpanel.datamodel.ServerDescriptor; 068import org.opends.guitools.controlpanel.event.BrowseActionListener; 069import org.opends.guitools.controlpanel.event.ConfigurationChangeEvent; 070import org.opends.guitools.controlpanel.ui.renderer.BackupTableCellRenderer; 071import org.opends.guitools.controlpanel.util.BackgroundTask; 072import org.opends.guitools.controlpanel.util.Utilities; 073import org.opends.quicksetup.Installation; 074import org.opends.server.types.BackupDirectory; 075import org.opends.server.types.BackupInfo; 076import org.opends.server.util.StaticUtils; 077 078/** Abstract class used to refactor code in panels that contain a backup list on it. */ 079public abstract class BackupListPanel extends StatusGenericPanel 080{ 081 private static final long serialVersionUID = -4804555239922795163L; 082 083 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 084 085 /** The refreshing list message, displayed when the list of backups is refreshed. */ 086 protected static final LocalizableMessage REFRESHING_LIST = INFO_CTRL_PANEL_REFRESHING_LIST_SUMMARY.get(); 087 088 /** The message informing that no backups where found. */ 089 protected static final LocalizableMessage NO_BACKUPS_FOUND = INFO_CTRL_PANEL_NO_BACKUPS_FOUND.get(); 090 091 private static final String DUMMY_PARENT_PATH = "/local/OpenDJ-X.X.X/bak"; 092 093 /** The text field containing the parent directory. */ 094 protected JTextField parentDirectory; 095 096 /** Label for the path field. */ 097 protected JLabel lPath; 098 099 /** Label for the list. */ 100 protected JLabel lAvailableBackups; 101 102 /** Refreshing list label (displayed instead of the list when this one is being refreshed). */ 103 protected JLabel lRefreshingList; 104 105 /** Refresh list button. */ 106 protected JButton refreshList; 107 108 /** Verify backup button. */ 109 protected JButton verifyBackup; 110 111 /** Browse button. */ 112 protected JButton browse; 113 114 /** The scroll that contains the list of backups (actually is a table). */ 115 protected JScrollPane tableScroll; 116 117 /** The list of backups. */ 118 protected JTable backupList; 119 120 private JLabel lRemoteFileHelp; 121 122 /** Whether the backup parent directory has been initialized with a value. */ 123 private boolean backupDirectoryInitialized; 124 125 private BackupTableCellRenderer renderer; 126 127 /** Default constructor. */ 128 protected BackupListPanel() 129 { 130 super(); 131 } 132 133 @Override 134 public Component getPreferredFocusComponent() 135 { 136 return parentDirectory; 137 } 138 139 /** 140 * Returns the selected backup in the list. 141 * 142 * @return the selected backup in the list. 143 */ 144 protected BackupDescriptor getSelectedBackup() 145 { 146 BackupDescriptor backup = null; 147 int row = backupList.getSelectedRow(); 148 if (row != -1) 149 { 150 BackupTableModel model = (BackupTableModel) backupList.getModel(); 151 backup = model.get(row); 152 } 153 return backup; 154 } 155 156 /** 157 * Notification that the verify button was clicked. Whatever is required to be 158 * done must be done in this method. 159 */ 160 protected abstract void verifyBackupClicked(); 161 162 /** 163 * Creates the components and lays them in the panel. 164 * 165 * @param gbc 166 * the grid bag constraints to be used. 167 */ 168 protected void createLayout(GridBagConstraints gbc) 169 { 170 gbc.gridy++; 171 gbc.anchor = GridBagConstraints.WEST; 172 gbc.weightx = 0.0; 173 gbc.fill = GridBagConstraints.NONE; 174 gbc.gridwidth = 1; 175 gbc.insets.left = 0; 176 lPath = Utilities.createPrimaryLabel(INFO_CTRL_PANEL_BACKUP_PATH_LABEL.get()); 177 add(lPath, gbc); 178 179 gbc.gridx = 1; 180 gbc.insets.left = 10; 181 parentDirectory = Utilities.createLongTextField(); 182 gbc.weightx = 1.0; 183 gbc.fill = GridBagConstraints.HORIZONTAL; 184 add(parentDirectory, gbc); 185 browse = Utilities.createButton(INFO_CTRL_PANEL_BROWSE_BUTTON_LABEL.get()); 186 browse.setOpaque(false); 187 browse.addActionListener( 188 new BrowseActionListener(parentDirectory, BrowseActionListener.BrowseType.LOCATION_DIRECTORY, this)); 189 gbc.gridx = 2; 190 gbc.weightx = 0.0; 191 add(browse, gbc); 192 193 lRemoteFileHelp = Utilities.createInlineHelpLabel(INFO_CTRL_PANEL_REMOTE_SERVER_PATH.get()); 194 gbc.gridx = 1; 195 gbc.gridwidth = 2; 196 gbc.insets.top = 3; 197 gbc.insets.left = 10; 198 gbc.gridy++; 199 add(lRemoteFileHelp, gbc); 200 201 gbc.gridx = 0; 202 gbc.gridy++; 203 gbc.insets.top = 10; 204 gbc.insets.left = 0; 205 lAvailableBackups = Utilities.createPrimaryLabel(INFO_CTRL_PANEL_AVAILABLE_BACKUPS_LABEL.get()); 206 gbc.anchor = GridBagConstraints.NORTHWEST; 207 gbc.fill = GridBagConstraints.NONE; 208 gbc.gridwidth = 1; 209 add(lAvailableBackups, gbc); 210 211 gbc.gridx = 1; 212 gbc.gridwidth = 2; 213 gbc.weightx = 1.0; 214 gbc.weighty = 1.0; 215 gbc.fill = GridBagConstraints.BOTH; 216 gbc.insets.left = 10; 217 lRefreshingList = Utilities.createDefaultLabel(REFRESHING_LIST); 218 lRefreshingList.setHorizontalAlignment(SwingConstants.CENTER); 219 gbc.anchor = GridBagConstraints.CENTER; 220 add(lRefreshingList, gbc); 221 222 backupList = new JTable(); 223 // Done to provide a good size to the table. 224 BackupTableModel model = new BackupTableModel(); 225 for (BackupDescriptor backup : createDummyBackupList()) 226 { 227 model.add(backup); 228 } 229 backupList.setModel(model); 230 backupList.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 231 backupList.setShowGrid(false); 232 backupList.setIntercellSpacing(new Dimension(0, 0)); 233 renderer = new BackupTableCellRenderer(); 234 renderer.setParentPath(new File(DUMMY_PARENT_PATH)); 235 for (int i = 0; i < model.getColumnCount(); i++) 236 { 237 TableColumn col = backupList.getColumn(model.getColumnName(i)); 238 col.setCellRenderer(renderer); 239 } 240 backupList.setTableHeader(null); 241 Utilities.updateTableSizes(backupList); 242 tableScroll = Utilities.createScrollPane(backupList); 243 tableScroll.setColumnHeaderView(null); 244 tableScroll.setPreferredSize(backupList.getPreferredSize()); 245 gbc.anchor = GridBagConstraints.NORTHWEST; 246 add(tableScroll, gbc); 247 lRefreshingList.setPreferredSize(tableScroll.getPreferredSize()); 248 249 gbc.gridy++; 250 gbc.anchor = GridBagConstraints.EAST; 251 gbc.weightx = 0.0; 252 gbc.weighty = 0.0; 253 gbc.insets.top = 5; 254 JPanel buttonPanel = new JPanel(new GridBagLayout()); 255 buttonPanel.setOpaque(false); 256 add(buttonPanel, gbc); 257 GridBagConstraints gbc2 = new GridBagConstraints(); 258 gbc2.gridx = 0; 259 gbc2.gridy = 0; 260 gbc2.gridwidth = 1; 261 gbc2.anchor = GridBagConstraints.EAST; 262 gbc2.fill = GridBagConstraints.HORIZONTAL; 263 gbc2.weightx = 1.0; 264 buttonPanel.add(Box.createHorizontalGlue(), gbc2); 265 refreshList = Utilities.createButton(INFO_CTRL_PANEL_REFRESH_LIST_BUTTON_LABEL.get()); 266 refreshList.setOpaque(false); 267 refreshList.addActionListener(new ActionListener() 268 { 269 @Override 270 public void actionPerformed(ActionEvent ev) 271 { 272 refreshList(); 273 } 274 }); 275 gbc2.weightx = 0.0; 276 gbc2.gridx++; 277 buttonPanel.add(refreshList, gbc2); 278 gbc2.gridx++; 279 gbc2.insets.left = 5; 280 verifyBackup = Utilities.createButton(INFO_CTRL_PANEL_VERIFY_BACKUP_BUTTON_LABEL.get()); 281 verifyBackup.setOpaque(false); 282 verifyBackup.addActionListener(new ActionListener() 283 { 284 @Override 285 public void actionPerformed(ActionEvent ev) 286 { 287 verifyBackupClicked(); 288 } 289 }); 290 ListSelectionListener listener = new ListSelectionListener() 291 { 292 @Override 293 public void valueChanged(ListSelectionEvent ev) 294 { 295 BackupDescriptor backup = getSelectedBackup(); 296 verifyBackup.setEnabled(backup != null); 297 } 298 }; 299 backupList.getSelectionModel().addListSelectionListener(listener); 300 listener.valueChanged(null); 301 buttonPanel.add(verifyBackup, gbc2); 302 } 303 304 /** 305 * Refresh the list of backups by looking in the backups defined under the 306 * provided parent backup directory. 307 */ 308 protected void refreshList() 309 { 310 final boolean refreshEnabled = refreshList.isEnabled(); 311 refreshList.setEnabled(false); 312 verifyBackup.setEnabled(false); 313 tableScroll.setVisible(false); 314 lRefreshingList.setText(REFRESHING_LIST.toString()); 315 lRefreshingList.setVisible(isLocal()); 316 317 final int lastSelectedRow = backupList.getSelectedRow(); 318 final String parentPath = parentDirectory.getText(); 319 320 BackgroundTask<Set<BackupInfo>> worker = new BackgroundTask<Set<BackupInfo>>() 321 { 322 @Override 323 public Set<BackupInfo> processBackgroundTask() throws Throwable 324 { 325 // Open the backup directory and make sure it is valid. 326 Set<BackupInfo> backups = new LinkedHashSet<>(); 327 Throwable firstThrowable = null; 328 329 if (new File(parentPath, BACKUP_DIRECTORY_DESCRIPTOR_FILE).exists()) 330 { 331 try 332 { 333 BackupDirectory backupDir = BackupDirectory.readBackupDirectoryDescriptor(parentPath); 334 backups.addAll(backupDir.getBackups().values()); 335 } 336 catch (Throwable t) 337 { 338 firstThrowable = t; 339 } 340 } 341 342 // Check the subdirectories 343 File f = new File(parentPath); 344 345 // Check the first level of directories (we might have done 346 // a backup of one backend and then a backup of several backends under the same directory). 347 if (f.isDirectory()) 348 { 349 File[] children = f.listFiles(); 350 for (int i = 0; i < children.length; i++) 351 { 352 if (children[i].isDirectory()) 353 { 354 try 355 { 356 BackupDirectory backupDir = 357 BackupDirectory.readBackupDirectoryDescriptor(children[i].getAbsolutePath()); 358 359 backups.addAll(backupDir.getBackups().values()); 360 } 361 catch (Throwable t2) 362 { 363 if (!children[i].getName().equals("tasks") && firstThrowable != null) 364 { 365 logger.warn(LocalizableMessage.raw("Error searching backup: " + t2, t2)); 366 } 367 } 368 } 369 } 370 } 371 if (backups.isEmpty() && firstThrowable != null) 372 { 373 throw firstThrowable; 374 } 375 return backups; 376 } 377 378 @Override 379 public void backgroundTaskCompleted(Set<BackupInfo> returnValue, Throwable t) 380 { 381 BackupTableModel model = (BackupTableModel) backupList.getModel(); 382 model.clear(); 383 renderer.setParentPath(new File(parentPath)); 384 if (t == null) 385 { 386 performSuccessActions(returnValue, model); 387 } 388 else 389 { 390 performErrorActions(t, model); 391 } 392 393 refreshList.setEnabled(refreshEnabled); 394 verifyBackup.setEnabled(getSelectedBackup() != null); 395 if (lastSelectedRow != -1 && lastSelectedRow < backupList.getRowCount()) 396 { 397 backupList.setRowSelectionInterval(lastSelectedRow, lastSelectedRow); 398 } 399 else if (backupList.getRowCount() > 0) 400 { 401 backupList.setRowSelectionInterval(0, 0); 402 } 403 } 404 405 private void performSuccessActions(Set<BackupInfo> returnValue, BackupTableModel model) 406 { 407 if (!returnValue.isEmpty()) 408 { 409 for (BackupInfo backup : returnValue) 410 { 411 model.add(new BackupDescriptor(backup)); 412 } 413 Utilities.updateTableSizes(backupList); 414 tableScroll.setVisible(true); 415 lRefreshingList.setVisible(false); 416 } 417 else 418 { 419 lRefreshingList.setText(NO_BACKUPS_FOUND.toString()); 420 lRefreshingList.setVisible(isLocal()); 421 } 422 updateUI(true, model); 423 } 424 425 private void performErrorActions(Throwable t, BackupTableModel model) 426 { 427 LocalizableMessage details = ERR_RESTOREDB_CANNOT_READ_BACKUP_DIRECTORY.get( 428 parentDirectory.getText(), StaticUtils.getExceptionMessage(t)); 429 updateErrorPane(errorPane, 430 ERR_ERROR_SEARCHING_BACKUPS_SUMMARY.get(), 431 ColorAndFontConstants.errorTitleFont, 432 details, 433 errorPane.getFont()); 434 packParentDialog(); 435 updateUI(false, model); 436 } 437 438 private void updateUI(boolean isSuccess, BackupTableModel model) 439 { 440 model.fireTableDataChanged(); 441 errorPane.setVisible(!isSuccess); 442 if (isSuccess) 443 { 444 // This is done to perform checks against whether we require to display an error message. 445 configurationChanged(new ConfigurationChangeEvent(null, getInfo().getServerDescriptor())); 446 } 447 else 448 { 449 lRefreshingList.setText(NO_BACKUPS_FOUND.toString()); 450 } 451 } 452 }; 453 worker.startBackgroundTask(); 454 } 455 456 457 /** 458 * Creates a list with backup descriptor. 459 * This is done simply to have a good initial size for the table. 460 * 461 * @return a list with bogus backup descriptors. 462 */ 463 private List<BackupDescriptor> createDummyBackupList() 464 { 465 List<BackupDescriptor> list = new ArrayList<>(); 466 list.add(new BackupDescriptor(new File(DUMMY_PARENT_PATH + "/200704201567Z"), 467 new GregorianCalendar(2007, 5, 20, 8, 10).getTime(), BackupDescriptor.Type.FULL, "id")); 468 list.add(new BackupDescriptor(new File(DUMMY_PARENT_PATH + "/200704201567Z"), 469 new GregorianCalendar(2007, 5, 22, 8, 10).getTime(), BackupDescriptor.Type.INCREMENTAL, "id")); 470 list.add(new BackupDescriptor(new File(DUMMY_PARENT_PATH + "/200704221567Z"), 471 new GregorianCalendar(2007, 5, 25, 8, 10).getTime(), BackupDescriptor.Type.INCREMENTAL, "id")); 472 return list; 473 } 474 475 @Override 476 public void configurationChanged(final ConfigurationChangeEvent ev) 477 { 478 if (!backupDirectoryInitialized && parentDirectory.getText().length() == 0) 479 { 480 SwingUtilities.invokeLater(new Runnable() 481 { 482 @Override 483 public void run() 484 { 485 parentDirectory.setText(getBackupPath(ev.getNewDescriptor())); 486 refreshList(); 487 backupDirectoryInitialized = true; 488 } 489 }); 490 } 491 492 SwingUtilities.invokeLater(new Runnable() 493 { 494 @Override 495 public void run() 496 { 497 lRemoteFileHelp.setVisible(!isLocal()); 498 browse.setVisible(isLocal()); 499 lAvailableBackups.setVisible(isLocal()); 500 tableScroll.setVisible(isLocal()); 501 refreshList.setVisible(isLocal()); 502 verifyBackup.setVisible(isLocal()); 503 } 504 }); 505 } 506 507 private String getBackupPath(ServerDescriptor desc) 508 { 509 if (desc.isLocal() || desc.isWindows() == isWindows()) 510 { 511 File f = new File(desc.getInstancePath(), Installation.BACKUPS_PATH_RELATIVE); 512 try 513 { 514 return f.getCanonicalPath(); 515 } 516 catch (Throwable t) 517 { 518 return f.getAbsolutePath(); 519 } 520 } 521 else 522 { 523 String separator = desc.isWindows() ? "\\" : "/"; 524 return desc.getInstancePath() + separator + Installation.BACKUPS_PATH_RELATIVE; 525 } 526 } 527 528 @Override 529 public void toBeDisplayed(boolean visible) 530 { 531 if (visible && backupDirectoryInitialized) 532 { 533 refreshList(); 534 } 535 } 536}