/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt * or http://forgerock.org/license/CDDLv1.0.html. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at legal-notices/CDDLv1_0.txt. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2008-2010 Sun Microsystems, Inc. * Portions Copyright 2011-2015 ForgeRock AS */ package org.opends.guitools.controlpanel.ui; import static org.opends.messages.AdminToolMessages.*; import static org.opends.messages.ToolMessages.*; import static org.opends.server.util.ServerConstants.*; import static com.forgerock.opendj.util.OperatingSystem.*; import java.awt.Component; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; import java.util.ArrayList; import java.util.GregorianCalendar; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import javax.swing.Box; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.ListSelectionModel; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.table.TableColumn; import org.forgerock.i18n.LocalizableMessage; import org.forgerock.i18n.slf4j.LocalizedLogger; import org.opends.guitools.controlpanel.datamodel.BackupDescriptor; import org.opends.guitools.controlpanel.datamodel.BackupTableModel; import org.opends.guitools.controlpanel.datamodel.ServerDescriptor; import org.opends.guitools.controlpanel.event.BrowseActionListener; import org.opends.guitools.controlpanel.event.ConfigurationChangeEvent; import org.opends.guitools.controlpanel.ui.renderer.BackupTableCellRenderer; import org.opends.guitools.controlpanel.util.BackgroundTask; import org.opends.guitools.controlpanel.util.Utilities; import org.opends.quicksetup.Installation; import org.opends.server.types.BackupDirectory; import org.opends.server.types.BackupInfo; import org.opends.server.util.StaticUtils; /** Abstract class used to refactor code in panels that contain a backup list on it. */ public abstract class BackupListPanel extends StatusGenericPanel { private static final long serialVersionUID = -4804555239922795163L; private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); /** The refreshing list message, displayed when the list of backups is refreshed. */ protected static final LocalizableMessage REFRESHING_LIST = INFO_CTRL_PANEL_REFRESHING_LIST_SUMMARY.get(); /** The message informing that no backups where found. */ protected static final LocalizableMessage NO_BACKUPS_FOUND = INFO_CTRL_PANEL_NO_BACKUPS_FOUND.get(); private static final String DUMMY_PARENT_PATH = "/local/OpenDJ-X.X.X/bak"; /** The text field containing the parent directory. */ protected JTextField parentDirectory; /** Label for the path field. */ protected JLabel lPath; /** Label for the list. */ protected JLabel lAvailableBackups; /** Refreshing list label (displayed instead of the list when this one is being refreshed). */ protected JLabel lRefreshingList; /** Refresh list button. */ protected JButton refreshList; /** Verify backup button. */ protected JButton verifyBackup; /** Browse button. */ protected JButton browse; /** The scroll that contains the list of backups (actually is a table). */ protected JScrollPane tableScroll; /** The list of backups. */ protected JTable backupList; private JLabel lRemoteFileHelp; /** Whether the backup parent directory has been initialized with a value. */ private boolean backupDirectoryInitialized; private BackupTableCellRenderer renderer; /** Default constructor. */ protected BackupListPanel() { super(); } @Override public Component getPreferredFocusComponent() { return parentDirectory; } /** * Returns the selected backup in the list. * * @return the selected backup in the list. */ protected BackupDescriptor getSelectedBackup() { BackupDescriptor backup = null; int row = backupList.getSelectedRow(); if (row != -1) { BackupTableModel model = (BackupTableModel) backupList.getModel(); backup = model.get(row); } return backup; } /** * Notification that the verify button was clicked. Whatever is required to be * done must be done in this method. */ protected abstract void verifyBackupClicked(); /** * Creates the components and lays them in the panel. * * @param gbc * the grid bag constraints to be used. */ protected void createLayout(GridBagConstraints gbc) { gbc.gridy++; gbc.anchor = GridBagConstraints.WEST; gbc.weightx = 0.0; gbc.fill = GridBagConstraints.NONE; gbc.gridwidth = 1; gbc.insets.left = 0; lPath = Utilities.createPrimaryLabel(INFO_CTRL_PANEL_BACKUP_PATH_LABEL.get()); add(lPath, gbc); gbc.gridx = 1; gbc.insets.left = 10; parentDirectory = Utilities.createLongTextField(); gbc.weightx = 1.0; gbc.fill = GridBagConstraints.HORIZONTAL; add(parentDirectory, gbc); browse = Utilities.createButton(INFO_CTRL_PANEL_BROWSE_BUTTON_LABEL.get()); browse.setOpaque(false); browse.addActionListener( new BrowseActionListener(parentDirectory, BrowseActionListener.BrowseType.LOCATION_DIRECTORY, this)); gbc.gridx = 2; gbc.weightx = 0.0; add(browse, gbc); lRemoteFileHelp = Utilities.createInlineHelpLabel(INFO_CTRL_PANEL_REMOTE_SERVER_PATH.get()); gbc.gridx = 1; gbc.gridwidth = 2; gbc.insets.top = 3; gbc.insets.left = 10; gbc.gridy++; add(lRemoteFileHelp, gbc); gbc.gridx = 0; gbc.gridy++; gbc.insets.top = 10; gbc.insets.left = 0; lAvailableBackups = Utilities.createPrimaryLabel(INFO_CTRL_PANEL_AVAILABLE_BACKUPS_LABEL.get()); gbc.anchor = GridBagConstraints.NORTHWEST; gbc.fill = GridBagConstraints.NONE; gbc.gridwidth = 1; add(lAvailableBackups, gbc); gbc.gridx = 1; gbc.gridwidth = 2; gbc.weightx = 1.0; gbc.weighty = 1.0; gbc.fill = GridBagConstraints.BOTH; gbc.insets.left = 10; lRefreshingList = Utilities.createDefaultLabel(REFRESHING_LIST); lRefreshingList.setHorizontalAlignment(SwingConstants.CENTER); gbc.anchor = GridBagConstraints.CENTER; add(lRefreshingList, gbc); backupList = new JTable(); // Done to provide a good size to the table. BackupTableModel model = new BackupTableModel(); for (BackupDescriptor backup : createDummyBackupList()) { model.add(backup); } backupList.setModel(model); backupList.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); backupList.setShowGrid(false); backupList.setIntercellSpacing(new Dimension(0, 0)); renderer = new BackupTableCellRenderer(); renderer.setParentPath(new File(DUMMY_PARENT_PATH)); for (int i = 0; i < model.getColumnCount(); i++) { TableColumn col = backupList.getColumn(model.getColumnName(i)); col.setCellRenderer(renderer); } backupList.setTableHeader(null); Utilities.updateTableSizes(backupList); tableScroll = Utilities.createScrollPane(backupList); tableScroll.setColumnHeaderView(null); tableScroll.setPreferredSize(backupList.getPreferredSize()); gbc.anchor = GridBagConstraints.NORTHWEST; add(tableScroll, gbc); lRefreshingList.setPreferredSize(tableScroll.getPreferredSize()); gbc.gridy++; gbc.anchor = GridBagConstraints.EAST; gbc.weightx = 0.0; gbc.weighty = 0.0; gbc.insets.top = 5; JPanel buttonPanel = new JPanel(new GridBagLayout()); buttonPanel.setOpaque(false); add(buttonPanel, gbc); GridBagConstraints gbc2 = new GridBagConstraints(); gbc2.gridx = 0; gbc2.gridy = 0; gbc2.gridwidth = 1; gbc2.anchor = GridBagConstraints.EAST; gbc2.fill = GridBagConstraints.HORIZONTAL; gbc2.weightx = 1.0; buttonPanel.add(Box.createHorizontalGlue(), gbc2); refreshList = Utilities.createButton(INFO_CTRL_PANEL_REFRESH_LIST_BUTTON_LABEL.get()); refreshList.setOpaque(false); refreshList.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent ev) { refreshList(); } }); gbc2.weightx = 0.0; gbc2.gridx++; buttonPanel.add(refreshList, gbc2); gbc2.gridx++; gbc2.insets.left = 5; verifyBackup = Utilities.createButton(INFO_CTRL_PANEL_VERIFY_BACKUP_BUTTON_LABEL.get()); verifyBackup.setOpaque(false); verifyBackup.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent ev) { verifyBackupClicked(); } }); ListSelectionListener listener = new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent ev) { BackupDescriptor backup = getSelectedBackup(); verifyBackup.setEnabled(backup != null); } }; backupList.getSelectionModel().addListSelectionListener(listener); listener.valueChanged(null); buttonPanel.add(verifyBackup, gbc2); } /** * Refresh the list of backups by looking in the backups defined under the * provided parent backup directory. */ protected void refreshList() { final boolean refreshEnabled = refreshList.isEnabled(); refreshList.setEnabled(false); verifyBackup.setEnabled(false); tableScroll.setVisible(false); lRefreshingList.setText(REFRESHING_LIST.toString()); lRefreshingList.setVisible(isLocal()); final int lastSelectedRow = backupList.getSelectedRow(); final String parentPath = parentDirectory.getText(); BackgroundTask> worker = new BackgroundTask>() { @Override public Set processBackgroundTask() throws Throwable { // Open the backup directory and make sure it is valid. Set backups = new LinkedHashSet<>(); Throwable firstThrowable = null; if (new File(parentPath, BACKUP_DIRECTORY_DESCRIPTOR_FILE).exists()) { try { BackupDirectory backupDir = BackupDirectory.readBackupDirectoryDescriptor(parentPath); backups.addAll(backupDir.getBackups().values()); } catch (Throwable t) { firstThrowable = t; } } // Check the subdirectories File f = new File(parentPath); // Check the first level of directories (we might have done // a backup of one backend and then a backup of several backends under the same directory). if (f.isDirectory()) { File[] children = f.listFiles(); for (int i = 0; i < children.length; i++) { if (children[i].isDirectory()) { try { BackupDirectory backupDir = BackupDirectory.readBackupDirectoryDescriptor(children[i].getAbsolutePath()); backups.addAll(backupDir.getBackups().values()); } catch (Throwable t2) { if (!children[i].getName().equals("tasks") && firstThrowable != null) { logger.warn(LocalizableMessage.raw("Error searching backup: " + t2, t2)); } } } } } if (backups.isEmpty() && firstThrowable != null) { throw firstThrowable; } return backups; } @Override public void backgroundTaskCompleted(Set returnValue, Throwable t) { BackupTableModel model = (BackupTableModel) backupList.getModel(); model.clear(); renderer.setParentPath(new File(parentPath)); if (t == null) { performSuccessActions(returnValue, model); } else { performErrorActions(t, model); } refreshList.setEnabled(refreshEnabled); verifyBackup.setEnabled(getSelectedBackup() != null); if (lastSelectedRow != -1 && lastSelectedRow < backupList.getRowCount()) { backupList.setRowSelectionInterval(lastSelectedRow, lastSelectedRow); } else if (backupList.getRowCount() > 0) { backupList.setRowSelectionInterval(0, 0); } } private void performSuccessActions(Set returnValue, BackupTableModel model) { if (!returnValue.isEmpty()) { for (BackupInfo backup : returnValue) { model.add(new BackupDescriptor(backup)); } Utilities.updateTableSizes(backupList); tableScroll.setVisible(true); lRefreshingList.setVisible(false); } else { lRefreshingList.setText(NO_BACKUPS_FOUND.toString()); lRefreshingList.setVisible(isLocal()); } updateUI(true, model); } private void performErrorActions(Throwable t, BackupTableModel model) { LocalizableMessage details = ERR_RESTOREDB_CANNOT_READ_BACKUP_DIRECTORY.get( parentDirectory.getText(), StaticUtils.getExceptionMessage(t)); updateErrorPane(errorPane, ERR_ERROR_SEARCHING_BACKUPS_SUMMARY.get(), ColorAndFontConstants.errorTitleFont, details, errorPane.getFont()); packParentDialog(); updateUI(false, model); } private void updateUI(boolean isSuccess, BackupTableModel model) { model.fireTableDataChanged(); errorPane.setVisible(!isSuccess); if (isSuccess) { // This is done to perform checks against whether we require to display an error message. configurationChanged(new ConfigurationChangeEvent(null, getInfo().getServerDescriptor())); } else { lRefreshingList.setText(NO_BACKUPS_FOUND.toString()); } } }; worker.startBackgroundTask(); } /** * Creates a list with backup descriptor. * This is done simply to have a good initial size for the table. * * @return a list with bogus backup descriptors. */ private List createDummyBackupList() { List list = new ArrayList<>(); list.add(new BackupDescriptor(new File(DUMMY_PARENT_PATH + "/200704201567Z"), new GregorianCalendar(2007, 5, 20, 8, 10).getTime(), BackupDescriptor.Type.FULL, "id")); list.add(new BackupDescriptor(new File(DUMMY_PARENT_PATH + "/200704201567Z"), new GregorianCalendar(2007, 5, 22, 8, 10).getTime(), BackupDescriptor.Type.INCREMENTAL, "id")); list.add(new BackupDescriptor(new File(DUMMY_PARENT_PATH + "/200704221567Z"), new GregorianCalendar(2007, 5, 25, 8, 10).getTime(), BackupDescriptor.Type.INCREMENTAL, "id")); return list; } @Override public void configurationChanged(final ConfigurationChangeEvent ev) { if (!backupDirectoryInitialized && parentDirectory.getText().length() == 0) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { parentDirectory.setText(getBackupPath(ev.getNewDescriptor())); refreshList(); backupDirectoryInitialized = true; } }); } SwingUtilities.invokeLater(new Runnable() { @Override public void run() { lRemoteFileHelp.setVisible(!isLocal()); browse.setVisible(isLocal()); lAvailableBackups.setVisible(isLocal()); tableScroll.setVisible(isLocal()); refreshList.setVisible(isLocal()); verifyBackup.setVisible(isLocal()); } }); } private String getBackupPath(ServerDescriptor desc) { if (desc.isLocal() || desc.isWindows() == isWindows()) { File f = new File(desc.getInstancePath(), Installation.BACKUPS_PATH_RELATIVE); try { return f.getCanonicalPath(); } catch (Throwable t) { return f.getAbsolutePath(); } } else { String separator = desc.isWindows() ? "\\" : "/"; return desc.getInstancePath() + separator + Installation.BACKUPS_PATH_RELATIVE; } } @Override public void toBeDisplayed(boolean visible) { if (visible && backupDirectoryInitialized) { refreshList(); } } }