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