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}