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