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}