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