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