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 2014-2015 ForgeRock AS
026 */
027package org.opends.guitools.controlpanel.ui;
028
029import static org.opends.messages.AdminToolMessages.*;
030
031import java.awt.CardLayout;
032import java.awt.Component;
033import java.awt.GridBagConstraints;
034import java.awt.Insets;
035import java.awt.event.ActionEvent;
036import java.awt.event.ActionListener;
037import java.util.ArrayList;
038import java.util.Arrays;
039import java.util.List;
040
041import javax.swing.JButton;
042import javax.swing.JPanel;
043import javax.swing.SwingUtilities;
044import javax.swing.border.Border;
045import javax.swing.border.EmptyBorder;
046import javax.swing.tree.TreePath;
047
048import org.forgerock.i18n.LocalizableMessage;
049import org.opends.guitools.controlpanel.browser.BasicNodeError;
050import org.opends.guitools.controlpanel.browser.BrowserController;
051import org.opends.guitools.controlpanel.datamodel.ControlPanelInfo;
052import org.opends.guitools.controlpanel.datamodel.CustomSearchResult;
053import org.opends.guitools.controlpanel.datamodel.ServerDescriptor;
054import org.opends.guitools.controlpanel.event.ConfigurationChangeEvent;
055import org.opends.guitools.controlpanel.event.EntryReadErrorEvent;
056import org.opends.guitools.controlpanel.event.EntryReadEvent;
057import org.opends.guitools.controlpanel.event.EntryReadListener;
058import org.opends.guitools.controlpanel.event.LDAPEntryChangedEvent;
059import org.opends.guitools.controlpanel.event.LDAPEntryChangedListener;
060import org.opends.guitools.controlpanel.task.DeleteEntryTask;
061import org.opends.guitools.controlpanel.task.ModifyEntryTask;
062import org.opends.guitools.controlpanel.task.Task;
063import org.opends.guitools.controlpanel.util.Utilities;
064import org.opends.server.config.ConfigConstants;
065import org.opends.server.types.DN;
066import org.opends.server.types.Entry;
067import org.opends.server.types.OpenDsException;
068import org.opends.server.util.ServerConstants;
069
070/** This is the panel that contains all the different views to display an entry. */
071public class LDAPEntryPanel extends StatusGenericPanel
072implements EntryReadListener
073{
074  private static final long serialVersionUID = -6608246173472437830L;
075  private JButton saveChanges;
076  private JButton delete;
077  private JPanel mainPanel;
078  private CardLayout cardLayout;
079
080  private ErrorSearchingEntryPanel errorSearchingPanel;
081  private LDIFViewEntryPanel ldifEntryPanel;
082  private TableViewEntryPanel tableEntryPanel;
083  private SimplifiedViewEntryPanel simplifiedEntryPanel;
084
085  private ViewEntryPanel displayedEntryPanel;
086
087  private CustomSearchResult searchResult;
088  private BrowserController controller;
089  private TreePath treePath;
090
091  private ModifyEntryTask newTask;
092
093  private final String NOTHING_SELECTED = "Nothing Selected";
094  private final String MULTIPLE_SELECTED = "Multiple Selected";
095  private final String LDIF_VIEW = "LDIF View";
096  private final String ATTRIBUTE_VIEW = "Attribute View";
097  private final String SIMPLIFIED_VIEW = "Simplified View";
098  private final String ERROR_SEARCHING = "Error Searching";
099
100  private View view = View.SIMPLIFIED_VIEW;
101
102  /** The different views that we have to display an LDAP entry. */
103  public enum View
104  {
105    /** Simplified view. */
106    SIMPLIFIED_VIEW,
107    /** Attribute view (contained in a table). */
108    ATTRIBUTE_VIEW,
109    /** LDIF view (text based). */
110    LDIF_VIEW
111  }
112
113  /** Default constructor. */
114  public LDAPEntryPanel()
115  {
116    super();
117    createLayout();
118  }
119
120  /** Creates the layout of the panel (but the contents are not populated here). */
121  private void createLayout()
122  {
123    GridBagConstraints gbc = new GridBagConstraints();
124    cardLayout = new CardLayout();
125    mainPanel = new JPanel(cardLayout);
126    mainPanel.setOpaque(false);
127    gbc.gridx = 0;
128    gbc.gridy = 0;
129    gbc.gridwidth = 2;
130    gbc.weightx = 1.0;
131    gbc.weighty = 1.0;
132    gbc.fill = GridBagConstraints.BOTH;
133    add(mainPanel, gbc);
134    gbc.gridwidth = 1;
135    gbc.anchor = GridBagConstraints.WEST;
136    gbc.insets = new Insets(5, 5, 5, 5);
137    gbc.weighty = 0.0;
138    gbc.gridy ++;
139    gbc.fill = GridBagConstraints.NONE;
140    delete = Utilities.createButton(INFO_CTRL_PANEL_DELETE_ENTRY_BUTTON.get());
141    delete.setOpaque(false);
142    add(delete, gbc);
143    delete.addActionListener(new ActionListener()
144    {
145      public void actionPerformed(ActionEvent ev)
146      {
147        deleteEntry();
148      }
149    });
150
151    gbc.anchor = GridBagConstraints.EAST;
152    gbc.gridx ++;
153    saveChanges =
154      Utilities.createButton(INFO_CTRL_PANEL_SAVE_CHANGES_LABEL.get());
155    saveChanges.setOpaque(false);
156    add(saveChanges, gbc);
157    saveChanges.addActionListener(new ActionListener()
158    {
159      /** {@inheritDoc} */
160      public void actionPerformed(ActionEvent ev)
161      {
162        saveChanges(true);
163      }
164    });
165
166    Border border = new EmptyBorder(10, 10, 10, 10);
167
168    NoItemSelectedPanel noEntryPanel = new NoItemSelectedPanel();
169    noEntryPanel.setMessage(INFO_CTRL_PANEL_NO_ENTRY_SELECTED_LABEL.get());
170    Utilities.setBorder(noEntryPanel, border);
171    mainPanel.add(noEntryPanel, NOTHING_SELECTED);
172
173    NoItemSelectedPanel multipleEntryPanel = new NoItemSelectedPanel();
174    multipleEntryPanel.setMessage(
175        INFO_CTRL_PANEL_MULTIPLE_ENTRIES_SELECTED_LABEL.get());
176    Utilities.setBorder(multipleEntryPanel, border);
177    mainPanel.add(multipleEntryPanel, MULTIPLE_SELECTED);
178
179    errorSearchingPanel = new ErrorSearchingEntryPanel();
180    if (errorSearchingPanel.requiresBorder())
181    {
182      Utilities.setBorder(multipleEntryPanel, border);
183    }
184    mainPanel.add(errorSearchingPanel, ERROR_SEARCHING);
185
186    LDAPEntryChangedListener listener = new LDAPEntryChangedListener()
187    {
188      /** {@inheritDoc} */
189      public void entryChanged(LDAPEntryChangedEvent ev)
190      {
191        boolean enable = saveChanges.isVisible() &&
192            !authenticationRequired(getInfo().getServerDescriptor());
193        if (enable)
194        {
195          if (ev.getEntry() == null)
196          {
197            // Something changed that is wrong: assume the entry has been
198            // modified, when the user tries to save we will inform of the
199            // problem
200            enable = true;
201          }
202          else
203          {
204            boolean modified =
205              !Utilities.areDnsEqual(ev.getEntry().getName().toString(),
206                  searchResult.getDN()) ||
207                  !ModifyEntryTask.getModifications(ev.getEntry(), searchResult,
208                      getInfo()).isEmpty();
209            enable = modified;
210          }
211        }
212        saveChanges.setEnabled(enable);
213      }
214    };
215
216    ldifEntryPanel = new LDIFViewEntryPanel();
217    ldifEntryPanel.addLDAPEntryChangedListener(listener);
218    if (ldifEntryPanel.requiresBorder())
219    {
220      Utilities.setBorder(ldifEntryPanel, border);
221    }
222    mainPanel.add(ldifEntryPanel, LDIF_VIEW);
223
224    tableEntryPanel = new TableViewEntryPanel();
225    tableEntryPanel.addLDAPEntryChangedListener(listener);
226    if (tableEntryPanel.requiresBorder())
227    {
228      Utilities.setBorder(tableEntryPanel, border);
229    }
230    mainPanel.add(tableEntryPanel, ATTRIBUTE_VIEW);
231
232    simplifiedEntryPanel = new SimplifiedViewEntryPanel();
233    simplifiedEntryPanel.addLDAPEntryChangedListener(listener);
234    if (simplifiedEntryPanel.requiresBorder())
235    {
236      Utilities.setBorder(simplifiedEntryPanel, border);
237    }
238    mainPanel.add(simplifiedEntryPanel, SIMPLIFIED_VIEW);
239
240    cardLayout.show(mainPanel, NOTHING_SELECTED);
241  }
242
243  /** {@inheritDoc} */
244  public void okClicked()
245  {
246    // No ok button
247  }
248
249  /** {@inheritDoc} */
250  public void entryRead(EntryReadEvent ev)
251  {
252    searchResult = ev.getSearchResult();
253
254    updateEntryView(searchResult, treePath);
255  }
256
257  /**
258   * Updates the panel with the provided search result.
259   * @param searchResult the search result corresponding to the selected node.
260   * @param treePath the tree path of the selected node.
261   */
262  private void updateEntryView(CustomSearchResult searchResult,
263      TreePath treePath)
264  {
265    boolean isReadOnly = isReadOnly(searchResult.getDN());
266    boolean canDelete = canDelete(searchResult.getDN());
267
268    delete.setVisible(canDelete);
269    saveChanges.setVisible(!isReadOnly);
270    String cardKey;
271    switch (view)
272    {
273    case LDIF_VIEW:
274      displayedEntryPanel = ldifEntryPanel;
275      cardKey = LDIF_VIEW;
276      break;
277    case ATTRIBUTE_VIEW:
278      displayedEntryPanel = tableEntryPanel;
279      cardKey = ATTRIBUTE_VIEW;
280      break;
281    default:
282      displayedEntryPanel = simplifiedEntryPanel;
283      cardKey = SIMPLIFIED_VIEW;
284    }
285    displayedEntryPanel.update(searchResult, isReadOnly, treePath);
286    saveChanges.setEnabled(false);
287    cardLayout.show(mainPanel, cardKey);
288  }
289
290  /**
291   * Sets the view to be displayed by this panel.
292   * @param view the view.
293   */
294  public void setView(View view)
295  {
296    if (view != this.view)
297    {
298      this.view = view;
299      if (searchResult != null)
300      {
301        updateEntryView(searchResult, treePath);
302      }
303    }
304  }
305
306  /**
307   * Displays a message informing that an error occurred reading the entry.
308   * @param ev the entry read error event.
309   */
310  public void entryReadError(EntryReadErrorEvent ev)
311  {
312    searchResult = null;
313
314    errorSearchingPanel.setError(ev.getDN(), ev.getError());
315
316    delete.setVisible(false);
317    saveChanges.setVisible(false);
318
319    cardLayout.show(mainPanel, ERROR_SEARCHING);
320
321    displayedEntryPanel = null;
322  }
323
324  /**
325   * Displays a message informing that an error occurred resolving a referral.
326   * @param dn the DN of the local entry.
327   * @param referrals the list of referrals defined in the entry.
328   * @param error the error that occurred resolving the referral.
329   */
330  public void referralSolveError(String dn, String[] referrals,
331      BasicNodeError error)
332  {
333    searchResult = null;
334
335    errorSearchingPanel.setReferralError(dn, referrals, error);
336
337    delete.setVisible(false);
338    saveChanges.setVisible(false);
339
340    cardLayout.show(mainPanel, ERROR_SEARCHING);
341
342    displayedEntryPanel = null;
343  }
344
345  /** Displays a panel informing that nothing is selected. */
346  public void noEntrySelected()
347  {
348    searchResult = null;
349
350    delete.setVisible(false);
351    saveChanges.setVisible(false);
352
353    cardLayout.show(mainPanel, NOTHING_SELECTED);
354
355    displayedEntryPanel = null;
356  }
357
358  /** Displays a panel informing that multiple entries are selected. */
359  public void multipleEntriesSelected()
360  {
361    searchResult = null;
362
363    delete.setVisible(false);
364    saveChanges.setVisible(false);
365
366    cardLayout.show(mainPanel, MULTIPLE_SELECTED);
367
368    displayedEntryPanel = null;
369  }
370
371  /** {@inheritDoc} */
372  public GenericDialog.ButtonType getButtonType()
373  {
374    return GenericDialog.ButtonType.NO_BUTTON;
375  }
376
377  /** {@inheritDoc} */
378  public LocalizableMessage getTitle()
379  {
380    return INFO_CTRL_PANEL_EDIT_LDAP_ENTRY_TITLE.get();
381  }
382
383  /** {@inheritDoc} */
384  public Component getPreferredFocusComponent()
385  {
386    return saveChanges;
387  }
388
389  /** {@inheritDoc} */
390  public void configurationChanged(ConfigurationChangeEvent ev)
391  {
392    final ServerDescriptor desc = ev.getNewDescriptor();
393    SwingUtilities.invokeLater(new Runnable()
394    {
395      /** {@inheritDoc} */
396      public void run()
397      {
398        boolean isReadOnly = true;
399        boolean canDelete = false;
400        if (searchResult != null && desc.isAuthenticated())
401        {
402          isReadOnly = isReadOnly(searchResult.getDN());
403          canDelete = canDelete(searchResult.getDN());
404        }
405
406        delete.setVisible(canDelete);
407        saveChanges.setVisible(!isReadOnly);
408      }
409    });
410  }
411
412  /** {@inheritDoc} */
413  public void setInfo(ControlPanelInfo info)
414  {
415    super.setInfo(info);
416    simplifiedEntryPanel.setInfo(info);
417    ldifEntryPanel.setInfo(info);
418    tableEntryPanel.setInfo(info);
419    errorSearchingPanel.setInfo(info);
420  }
421
422  private List<DN> parentReadOnly;
423  private List<DN> nonDeletable;
424  {
425    try
426    {
427      parentReadOnly = Arrays.asList(
428        DN.valueOf(ConfigConstants.DN_TASK_ROOT),
429        DN.valueOf(ConfigConstants.DN_MONITOR_ROOT),
430        DN.valueOf(ConfigConstants.DN_BACKUP_ROOT),
431        DN.valueOf(ServerConstants.DN_EXTERNAL_CHANGELOG_ROOT)
432      );
433      nonDeletable = Arrays.asList(
434          DN.valueOf(ConfigConstants.DN_CONFIG_ROOT),
435          DN.valueOf(ConfigConstants.DN_DEFAULT_SCHEMA_ROOT),
436          DN.valueOf(ConfigConstants.DN_TRUST_STORE_ROOT)
437      );
438    }
439    catch (Throwable t)
440    {
441      throw new RuntimeException("Error decoding DNs: "+t, t);
442    }
443  }
444
445  /**
446   * Returns <CODE>true</CODE> if the provided DN corresponds to a read-only
447   * entry and <CODE>false</CODE> otherwise.
448   * @param sDn the DN of the entry.
449   * @return <CODE>true</CODE> if the provided DN corresponds to a read-only
450   * entry and <CODE>false</CODE> otherwise.
451   */
452  public boolean isReadOnly(String sDn)
453  {
454    boolean isReadOnly = false;
455    try
456    {
457      DN dn = DN.valueOf(sDn);
458      for (DN parentDN : parentReadOnly)
459      {
460        if (dn.isDescendantOf(parentDN))
461        {
462          isReadOnly = true;
463          break;
464        }
465      }
466      if (!isReadOnly)
467      {
468        isReadOnly = dn.equals(DN.NULL_DN);
469      }
470    }
471    catch (Throwable t)
472    {
473      throw new RuntimeException("Error decoding DNs: "+t, t);
474    }
475    return isReadOnly;
476  }
477
478  /**
479   * Returns <CODE>true</CODE> if the provided DN corresponds to an entry that
480   * can be deleted and <CODE>false</CODE> otherwise.
481   * @param sDn the DN of the entry.
482   * @return <CODE>true</CODE> if the provided DN corresponds to an entry that
483   * can be deleted and <CODE>false</CODE> otherwise.
484   */
485  public boolean canDelete(String sDn)
486  {
487    try
488    {
489      DN dn = DN.valueOf(sDn);
490      return !dn.equals(DN.NULL_DN)
491          && !nonDeletable.contains(dn)
492          && isDescendantOfAny(dn, parentReadOnly);
493    }
494    catch (Throwable t)
495    {
496      throw new RuntimeException("Error decoding DNs: "+t, t);
497    }
498  }
499
500  private boolean isDescendantOfAny(DN dn, List<DN> parentDNs)
501  {
502    for (DN parentDN : parentDNs)
503    {
504      if (dn.isDescendantOf(parentDN))
505      {
506        return false;
507      }
508    }
509    return true;
510  }
511
512  /**
513   * Saves the changes done to the entry.
514   * @param modal whether the progress dialog for the task must be modal or
515   * not.
516   */
517  private void saveChanges(boolean modal)
518  {
519    newTask = null;
520    final ArrayList<LocalizableMessage> errors = new ArrayList<>();
521    // Check that the entry is correct.
522    try
523    {
524      ProgressDialog dlg = new ProgressDialog(
525          Utilities.getFrame(this),
526          Utilities.getFrame(this),
527          INFO_CTRL_PANEL_MODIFYING_ENTRY_CHANGES_TITLE.get(), getInfo());
528      dlg.setModal(modal);
529      Entry entry = displayedEntryPanel.getEntry();
530      newTask = new ModifyEntryTask(getInfo(), dlg, entry,
531            searchResult, controller, treePath);
532      for (Task task : getInfo().getTasks())
533      {
534        task.canLaunch(newTask, errors);
535      }
536
537      if (errors.isEmpty())
538      {
539        if (newTask.hasModifications()) {
540          String dn = entry.getName().toString();
541          launchOperation(newTask,
542              INFO_CTRL_PANEL_MODIFYING_ENTRY_SUMMARY.get(dn),
543              INFO_CTRL_PANEL_MODIFYING_ENTRY_COMPLETE.get(),
544              INFO_CTRL_PANEL_MODIFYING_ENTRY_SUCCESSFUL.get(dn),
545              ERR_CTRL_PANEL_MODIFYING_ENTRY_ERROR_SUMMARY.get(),
546              ERR_CTRL_PANEL_MODIFYING_ENTRY_ERROR_DETAILS.get(dn),
547              null,
548              dlg);
549          saveChanges.setEnabled(false);
550          dlg.setVisible(true);
551        }
552        else
553        {
554          // Mark the panel as it has no changes.  This can happen because every
555          // time the user types something the saveChanges button is enabled
556          // (for performance reasons with huge entries).
557          saveChanges.setEnabled(false);
558        }
559      }
560    }
561    catch (OpenDsException ode)
562    {
563      errors.add(ERR_CTRL_PANEL_INVALID_ENTRY.get(ode.getMessageObject()));
564    }
565    if (!errors.isEmpty())
566    {
567      displayErrorDialog(errors);
568    }
569  }
570
571  private void deleteEntry()
572  {
573    final ArrayList<LocalizableMessage> errors = new ArrayList<>();
574    // Check that the entry is correct.
575    // Rely in numsubordinates and hassubordinates
576    boolean isLeaf = !BrowserController.getHasSubOrdinates(searchResult);
577
578    if (treePath != null)
579    {
580      LocalizableMessage title = isLeaf ? INFO_CTRL_PANEL_DELETING_ENTRY_TITLE.get() :
581        INFO_CTRL_PANEL_DELETING_SUBTREE_TITLE.get();
582      ProgressDialog dlg = new ProgressDialog(
583          Utilities.createFrame(),
584          Utilities.getParentDialog(this), title, getInfo());
585      DeleteEntryTask newTask = new DeleteEntryTask(getInfo(), dlg,
586          new TreePath[]{treePath}, controller);
587      for (Task task : getInfo().getTasks())
588      {
589        task.canLaunch(newTask, errors);
590      }
591      if (errors.isEmpty())
592      {
593        LocalizableMessage confirmationMessage =
594          isLeaf ? INFO_CTRL_PANEL_DELETE_ENTRY_CONFIRMATION_DETAILS.get(
595              searchResult.getDN()) :
596                INFO_CTRL_PANEL_DELETE_SUBTREE_CONFIRMATION_DETAILS.get(
597                    searchResult.getDN());
598          if (displayConfirmationDialog(
599              INFO_CTRL_PANEL_CONFIRMATION_REQUIRED_SUMMARY.get(),
600              confirmationMessage))
601          {
602            String dn = searchResult.getDN();
603            if (isLeaf)
604            {
605              launchOperation(newTask,
606                  INFO_CTRL_PANEL_DELETING_ENTRY_SUMMARY.get(dn),
607                  INFO_CTRL_PANEL_DELETING_ENTRY_COMPLETE.get(),
608                  INFO_CTRL_PANEL_DELETING_ENTRY_SUCCESSFUL.get(dn),
609                  ERR_CTRL_PANEL_DELETING_ENTRY_ERROR_SUMMARY.get(),
610                  ERR_CTRL_PANEL_DELETING_ENTRY_ERROR_DETAILS.get(dn),
611                  null,
612                  dlg);
613            }
614            else
615            {
616              launchOperation(newTask,
617                  INFO_CTRL_PANEL_DELETING_SUBTREE_SUMMARY.get(dn),
618                  INFO_CTRL_PANEL_DELETING_SUBTREE_COMPLETE.get(),
619                  INFO_CTRL_PANEL_DELETING_SUBTREE_SUCCESSFUL.get(dn),
620                  ERR_CTRL_PANEL_DELETING_SUBTREE_ERROR_SUMMARY.get(),
621                  ERR_CTRL_PANEL_DELETING_SUBTREE_ERROR_DETAILS.get(dn),
622                  null,
623                  dlg);
624            }
625            dlg.setVisible(true);
626          }
627      }
628    }
629    if (!errors.isEmpty())
630    {
631      displayErrorDialog(errors);
632    }
633  }
634
635  /**
636   * Returns the browser controller in charge of the tree.
637   * @return the browser controller in charge of the tree.
638   */
639  public BrowserController getController()
640  {
641    return controller;
642  }
643
644  /**
645   * Sets the browser controller in charge of the tree.
646   * @param controller the browser controller in charge of the tree.
647   */
648  public void setController(BrowserController controller)
649  {
650    this.controller = controller;
651  }
652
653  /**
654   * Returns the tree path associated with the node that is being displayed.
655   * @return the tree path associated with the node that is being displayed.
656   */
657  public TreePath getTreePath()
658  {
659    return treePath;
660  }
661
662  /**
663   * Sets the tree path associated with the node that is being displayed.
664   * @param treePath the tree path associated with the node that is being
665   * displayed.
666   */
667  public void setTreePath(TreePath treePath)
668  {
669    this.treePath = treePath;
670  }
671
672  /**
673   * Method used to know if there are unsaved changes or not.  It is used by
674   * the entry selection listener when the user changes the selection.
675   * @return <CODE>true</CODE> if there are unsaved changes (and so the
676   * selection of the entry should be cancelled) and <CODE>false</CODE>
677   * otherwise.
678   */
679  public boolean mustCheckUnsavedChanges()
680  {
681    return displayedEntryPanel != null &&
682        saveChanges.isVisible() && saveChanges.isEnabled();
683  }
684
685  /**
686   * Tells whether the user chose to save the changes in the panel, to not save
687   * them or simply canceled the selection change in the tree.
688   * @return the value telling whether the user chose to save the changes in the
689   * panel, to not save them or simply canceled the selection in the tree.
690   */
691  public UnsavedChangesDialog.Result checkUnsavedChanges()
692  {
693    UnsavedChangesDialog.Result result;
694    UnsavedChangesDialog unsavedChangesDlg = new UnsavedChangesDialog(
695          Utilities.getParentDialog(this), getInfo());
696    unsavedChangesDlg.setMessage(INFO_CTRL_PANEL_UNSAVED_CHANGES_SUMMARY.get(),
697       INFO_CTRL_PANEL_UNSAVED_ENTRY_CHANGES_DETAILS.get(searchResult.getDN()));
698    Utilities.centerGoldenMean(unsavedChangesDlg,
699          Utilities.getParentDialog(this));
700    unsavedChangesDlg.setVisible(true);
701    result = unsavedChangesDlg.getResult();
702    if (result == UnsavedChangesDialog.Result.SAVE)
703    {
704      saveChanges(false);
705      if (newTask == null || // The user data is not valid
706          newTask.getState() != Task.State.FINISHED_SUCCESSFULLY)
707      {
708        result = UnsavedChangesDialog.Result.CANCEL;
709      }
710    }
711
712    return result;
713  }
714}