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