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 2009-2010 Sun Microsystems, Inc.
025 *      Portions Copyright 2012-2015 ForgeRock AS
026 */
027
028package org.opends.guitools.controlpanel.ui;
029
030import static org.opends.messages.AdminToolMessages.*;
031import static com.forgerock.opendj.cli.Utils.isDN;
032
033import java.awt.Component;
034import java.awt.GridBagConstraints;
035import java.awt.event.ActionEvent;
036import java.awt.event.ActionListener;
037import java.io.IOException;
038import java.util.ArrayList;
039import java.util.List;
040
041import javax.naming.ldap.InitialLdapContext;
042import javax.swing.JButton;
043import javax.swing.JLabel;
044import javax.swing.JPasswordField;
045import javax.swing.JTextField;
046import javax.swing.event.DocumentEvent;
047import javax.swing.event.DocumentListener;
048
049import org.opends.guitools.controlpanel.browser.BrowserController;
050import org.opends.guitools.controlpanel.datamodel.CustomSearchResult;
051import org.opends.guitools.controlpanel.ui.nodes.BasicNode;
052import org.opends.guitools.controlpanel.util.BackgroundTask;
053import org.opends.guitools.controlpanel.util.LDAPEntryReader;
054import org.opends.guitools.controlpanel.util.Utilities;
055import org.forgerock.i18n.LocalizableMessage;
056import org.opends.server.types.DN;
057import org.opends.server.types.DirectoryException;
058import org.opends.server.util.Base64;
059import org.opends.server.util.LDIFException;
060import org.opends.server.util.ServerConstants;
061
062/**
063 * The panel used to duplicate an entry.
064 *
065 */
066public class DuplicateEntryPanel extends AbstractNewEntryPanel
067{
068  private static final long serialVersionUID = -9879879123123123L;
069  private JLabel lName;
070  private JTextField name;
071  private JLabel lParentDN;
072  private JTextField parentDN;
073  private JLabel lPassword;
074  private JPasswordField password = Utilities.createPasswordField(25);
075  private JLabel lconfirmPassword;
076  private JPasswordField confirmPassword = Utilities.createPasswordField(25);
077  private JLabel lPasswordInfo;
078  private JLabel dn;
079
080  private GenericDialog browseDlg;
081  private LDAPEntrySelectionPanel browsePanel;
082
083  private CustomSearchResult entryToDuplicate;
084  private String rdnAttribute;
085
086  /**
087   * Default constructor.
088   *
089   */
090  public DuplicateEntryPanel()
091  {
092    super();
093    createLayout();
094  }
095
096  /** {@inheritDoc} */
097  public Component getPreferredFocusComponent()
098  {
099    return name;
100  }
101
102  /** {@inheritDoc} */
103  public boolean requiresScroll()
104  {
105    return true;
106  }
107
108  /** {@inheritDoc} */
109  public void setParent(BasicNode parentNode, BrowserController controller)
110  {
111    throw new IllegalArgumentException("this method must not be called");
112  }
113
114  /**
115   * Sets the entry to be duplicated.
116   * @param node the node to be duplicated.
117   * @param controller the browser controller.
118   */
119  public void setEntryToDuplicate(BasicNode node,
120      BrowserController controller)
121  {
122    if (node == null)
123    {
124      throw new IllegalArgumentException("node is null.");
125    }
126
127    displayMessage(INFO_CTRL_PANEL_READING_SUMMARY.get());
128    setEnabledOK(false);
129
130    entryToDuplicate = null;
131    super.controller = controller;
132
133    DN aParentDN;
134    String aRdn;
135    try
136    {
137      DN nodeDN = DN.valueOf(node.getDN());
138      if (nodeDN.isRootDN())
139      {
140        aParentDN = nodeDN;
141        aRdn = "(1)";
142      }
143      else
144      {
145        aParentDN = nodeDN.parent();
146        aRdn = nodeDN.rdn().getAttributeValue(0) + "-1";
147      }
148    }
149    catch (DirectoryException de)
150    {
151      throw new IllegalStateException("Unexpected error decoding dn: '"+
152          node.getDN()+"' error: "+de, de);
153    }
154    parentDN.setText(aParentDN != null ? aParentDN.toString() : "");
155    name.setText(aRdn);
156    password.setText("");
157    confirmPassword.setText("");
158
159    readEntry(node);
160  }
161
162  /** {@inheritDoc} */
163  protected LocalizableMessage getProgressDialogTitle()
164  {
165    return INFO_CTRL_PANEL_DUPLICATE_ENTRY_TITLE.get();
166  }
167
168  /** {@inheritDoc} */
169  public LocalizableMessage getTitle()
170  {
171    return INFO_CTRL_PANEL_DUPLICATE_ENTRY_TITLE.get();
172  }
173
174  /**
175   * Creates the layout of the panel (but the contents are not populated here).
176   */
177  private void createLayout()
178  {
179    GridBagConstraints gbc = new GridBagConstraints();
180    gbc.gridx = 0;
181    gbc.gridy = 0;
182
183    addErrorPane(gbc);
184
185    gbc.gridy ++;
186    gbc.gridwidth = 1;
187    gbc.weightx = 0.0;
188    gbc.weighty = 0.0;
189    gbc.fill = GridBagConstraints.HORIZONTAL;
190
191    gbc.gridx = 0;
192    gbc.insets.left = 0;
193    lName = Utilities.createPrimaryLabel(
194        INFO_CTRL_PANEL_DUPLICATE_ENTRY_NAME_LABEL.get());
195    add(lName, gbc);
196    name = Utilities.createTextField("", 30);
197    gbc.weightx = 1.0;
198    gbc.gridwidth = 2;
199    gbc.weighty = 0.0;
200    gbc.insets.left = 10;
201    gbc.gridx = 1;
202    add(name, gbc);
203
204    gbc.gridy ++;
205    gbc.gridx = 0;
206    gbc.insets.top = 10;
207    gbc.insets.left = 0;
208    gbc.gridwidth = 1;
209    gbc.weightx = 0.0;
210
211    gbc.fill = GridBagConstraints.BOTH;
212    lParentDN = Utilities.createPrimaryLabel(
213        INFO_CTRL_PANEL_DUPLICATE_ENTRY_PARENT_DN_LABEL.get());
214    add(lParentDN, gbc);
215
216    parentDN = Utilities.createTextField("", 30);
217    gbc.weightx = 1.0;
218    gbc.weighty = 0.0;
219    gbc.insets.left = 10;
220    gbc.gridx = 1;
221    add(parentDN, gbc);
222
223    JButton browse = Utilities.createButton(
224            INFO_CTRL_PANEL_BROWSE_BUTTON_LABEL.get());
225    gbc.weightx = 0.0;
226    gbc.gridx = 2;
227    add(browse, gbc);
228    browse.addActionListener(new ActionListener()
229    {
230      /** {@inheritDoc} */
231      public void actionPerformed(ActionEvent ev)
232      {
233        browseClicked();
234      }
235    });
236
237    gbc.gridwidth = 1;
238    gbc.weightx = 0.0;
239    gbc.gridx = 0;
240    gbc.gridy ++;
241    gbc.insets.left = 0;
242    lPassword = Utilities.createPrimaryLabel(
243              INFO_CTRL_PANEL_DUPLICATE_ENTRY_NEWPASSWORD_LABEL.get());
244    add(lPassword, gbc);
245    gbc.weightx = 1.0;
246    gbc.gridwidth = 2;
247    gbc.weighty = 0.0;
248    gbc.insets.left = 10;
249    gbc.gridx = 1;
250    add(password, gbc);
251
252    gbc.gridwidth = 1;
253    gbc.weightx = 0.0;
254    gbc.gridx = 0;
255    gbc.gridy ++;
256    gbc.insets.left = 0;
257    lconfirmPassword = Utilities.createPrimaryLabel(
258              INFO_CTRL_PANEL_DUPLICATE_ENTRY_CONFIRMNEWPASSWORD_LABEL.get());
259    add(lconfirmPassword, gbc);
260    gbc.weightx = 1.0;
261    gbc.gridwidth = 2;
262    gbc.weighty = 0.0;
263    gbc.insets.left = 10;
264    gbc.gridx = 1;
265    add(confirmPassword, gbc);
266
267    gbc.gridx = 0;
268    gbc.gridy ++;
269    gbc.insets.left = 0;
270    lPasswordInfo = Utilities.createInlineHelpLabel(
271            INFO_CTRL_PANEL_DUPLICATE_ENTRY_PASSWORD_INFO.get());
272    gbc.gridwidth = 3;
273    add(lPasswordInfo, gbc);
274
275    gbc.gridwidth = 1;
276    gbc.gridx = 0;
277    gbc.gridy ++;
278    gbc.insets.left = 0;
279    add(Utilities.createPrimaryLabel(INFO_CTRL_PANEL_DUPLICATE_ENTRY_DN.get()),
280        gbc);
281    dn = Utilities.createDefaultLabel();
282
283    gbc.gridx = 1;
284    gbc.gridwidth = 2;
285    gbc.insets.left = 10;
286    add(dn, gbc);
287
288    DocumentListener listener = new DocumentListener()
289    {
290      /** {@inheritDoc} */
291      public void insertUpdate(DocumentEvent ev)
292      {
293        updateDNValue();
294      }
295
296      /** {@inheritDoc} */
297      public void changedUpdate(DocumentEvent ev)
298      {
299        insertUpdate(ev);
300      }
301
302      /** {@inheritDoc} */
303      public void removeUpdate(DocumentEvent ev)
304      {
305        insertUpdate(ev);
306      }
307    };
308    name.getDocument().addDocumentListener(listener);
309    parentDN.getDocument().addDocumentListener(listener);
310
311    addBottomGlue(gbc);
312  }
313
314  /** {@inheritDoc} */
315  protected void checkSyntax(ArrayList<LocalizableMessage> errors)
316  {
317    int origSize = errors.size();
318    String name = this.name.getText().trim();
319    setPrimaryValid(lName);
320    setPrimaryValid(lParentDN);
321    if (name.length() == 0)
322    {
323      errors.add(ERR_CTRL_PANEL_DUPLICATE_ENTRY_NAME_EMPTY.get());
324      setPrimaryInvalid(lName);
325    }
326    String parentDN = this.parentDN.getText().trim();
327    if (!isDN(parentDN))
328    {
329      errors.add(ERR_CTRL_PANEL_DUPLICATE_ENTRY_PARENT_DN_NOT_VALID.get());
330      setPrimaryInvalid(lParentDN);
331    }
332    else if (!entryExists(parentDN))
333    {
334      errors.add(ERR_CTRL_PANEL_DUPLICATE_ENTRY_PARENT_DOES_NOT_EXIST.get());
335      setPrimaryInvalid(lParentDN);
336    }
337
338    char[] pwd1 = password.getPassword();
339    char[] pwd2 = confirmPassword.getPassword();
340    String sPwd1 = new String(pwd1);
341    String sPwd2 = new String(pwd2);
342    if (!sPwd1.equals(sPwd2))
343    {
344          errors.add(ERR_CTRL_PANEL_PASSWORD_DO_NOT_MATCH.get());
345    }
346
347    if (errors.size() == origSize)
348    {
349      try
350      {
351        getEntry();
352      }
353      catch (IOException ioe)
354      {
355        errors.add(ERR_CTRL_PANEL_ERROR_CHECKING_ENTRY.get(ioe));
356      }
357      catch (LDIFException le)
358      {
359        errors.add(le.getMessageObject());
360      }
361    }
362  }
363
364  /** {@inheritDoc} */
365  protected String getLDIF()
366  {
367    String dn = this.dn.getText();
368    StringBuilder sb = new StringBuilder();
369    sb.append("dn: ").append(dn);
370    for (String attrName : entryToDuplicate.getAttributeNames())
371    {
372      List<Object> values = entryToDuplicate.getAttributeValues(attrName);
373      if (attrName.equalsIgnoreCase(ServerConstants.ATTR_USER_PASSWORD))
374      {
375        sb.append("\n");
376        String pwd = new String(password.getPassword());
377        if (!pwd.isEmpty())
378        {
379          sb.append(attrName).append(": ").append(pwd);
380        }
381      }
382      else if (!attrName.equalsIgnoreCase(rdnAttribute))
383      {
384        if (!ViewEntryPanel.isEditable(attrName,
385            getInfo().getServerDescriptor().getSchema()))
386        {
387          continue;
388        }
389        for (Object value : values)
390        {
391          sb.append("\n");
392          if (value instanceof byte[])
393          {
394            final String base64 = Base64.encode((byte[]) value);
395            sb.append(attrName).append(":: ").append(base64);
396          }
397          else
398          {
399            sb.append(attrName).append(": ").append(value);
400          }
401        }
402      }
403      else
404      {
405        String newValue;
406        try
407        {
408          DN theDN = DN.valueOf(dn);
409          newValue = theDN.rdn().getAttributeValue(0).toString();
410        }
411        catch (DirectoryException de)
412        {
413          throw new IllegalStateException("Unexpected error with dn: '"+dn+
414              "' "+de, de);
415        }
416        if (values.size() == 1)
417        {
418          sb.append("\n");
419          sb.append(attrName).append(": ").append(newValue);
420        }
421        else
422        {
423          String oldValue;
424          try
425          {
426            DN oldDN = DN.valueOf(entryToDuplicate.getDN());
427            oldValue = oldDN.rdn().getAttributeValue(0).toString();
428          }
429          catch (DirectoryException de)
430          {
431            throw new IllegalStateException("Unexpected error with dn: '"+
432                entryToDuplicate.getDN()+"' "+de, de);
433          }
434          for (Object value : values)
435          {
436            sb.append("\n");
437            if (oldValue.equals(value))
438            {
439              sb.append(attrName).append(": ").append(newValue);
440            }
441            else
442            {
443              sb.append(attrName).append(": ").append(value);
444            }
445          }
446        }
447      }
448    }
449    return sb.toString();
450  }
451
452  private void browseClicked()
453  {
454    if (browseDlg == null)
455    {
456      browsePanel = new LDAPEntrySelectionPanel();
457      browsePanel.setTitle(INFO_CTRL_PANEL_CHOOSE_PARENT_ENTRY_DN.get());
458      browsePanel.setFilter(
459          LDAPEntrySelectionPanel.Filter.DEFAULT);
460      browsePanel.setMultipleSelection(false);
461      browsePanel.setInfo(getInfo());
462      browseDlg = new GenericDialog(Utilities.getFrame(this),
463          browsePanel);
464      Utilities.centerGoldenMean(browseDlg,
465          Utilities.getParentDialog(this));
466      browseDlg.setModal(true);
467    }
468    browseDlg.setVisible(true);
469    String[] dns = browsePanel.getDNs();
470    if (dns.length > 0)
471    {
472      for (String dn : dns)
473      {
474        parentDN.setText(dn);
475      }
476    }
477  }
478
479  private void readEntry(final BasicNode node)
480  {
481    final long t1 = System.currentTimeMillis();
482    BackgroundTask<CustomSearchResult> task =
483      new BackgroundTask<CustomSearchResult>()
484    {
485      public CustomSearchResult processBackgroundTask() throws Throwable
486      {
487        InitialLdapContext ctx =
488          controller.findConnectionForDisplayedEntry(node);
489        LDAPEntryReader reader = new LDAPEntryReader(node.getDN(), ctx);
490        sleepIfRequired(700, t1);
491        return reader.processBackgroundTask();
492      }
493
494      public void backgroundTaskCompleted(CustomSearchResult sr,
495          Throwable throwable)
496      {
497        if (throwable != null)
498        {
499          LocalizableMessage title = INFO_CTRL_PANEL_ERROR_SEARCHING_ENTRY_TITLE.get();
500          LocalizableMessage details =
501            ERR_CTRL_PANEL_ERROR_SEARCHING_ENTRY.get(node.getDN(), throwable);
502          displayErrorMessage(title, details);
503        }
504        else
505        {
506          entryToDuplicate = sr;
507          try
508          {
509            DN dn = DN.valueOf(sr.getDN());
510            rdnAttribute = dn.rdn().getAttributeType(0).getNameOrOID();
511
512            updateDNValue();
513            Boolean hasPassword = !sr.getAttributeValues(
514                    ServerConstants.ATTR_USER_PASSWORD).isEmpty();
515            lPassword.setVisible(hasPassword);
516            password.setVisible(hasPassword);
517            lconfirmPassword.setVisible(hasPassword);
518            confirmPassword.setVisible(hasPassword);
519            lPasswordInfo.setVisible(hasPassword);
520            displayMainPanel();
521            setEnabledOK(true);
522          }
523          catch (DirectoryException de)
524          {
525            displayErrorMessage(INFO_CTRL_PANEL_ERROR_DIALOG_TITLE.get(),
526                de.getMessageObject());
527          }
528        }
529      }
530    };
531    task.startBackgroundTask();
532  }
533
534  private void updateDNValue()
535  {
536    String value = name.getText().trim();
537    // If it takes time to read the entry, the rdnAttribute might not be initialized yet. Don't try to use it then.
538    if (value.length() > 0 && rdnAttribute != null)
539    {
540       String rdn = Utilities.getRDNString(rdnAttribute, value);
541          dn.setText(rdn+","+parentDN.getText().trim());
542    }
543    else
544    {
545      dn.setText(","+parentDN.getText().trim());
546    }
547  }
548
549  private void sleepIfRequired(long sleepTime, long startTime)
550  {
551    long tSleep = sleepTime - (System.currentTimeMillis() - startTime);
552    if (tSleep > 0)
553    {
554      try
555      {
556        Thread.sleep(tSleep);
557      }
558      catch (Throwable t)
559      {
560      }
561    }
562  }
563}