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