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