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 2008-2010 Sun Microsystems, Inc.
025 *      Portions Copyright 2013-2015 ForgeRock AS.
026 */
027package org.opends.guitools.controlpanel.ui;
028
029import static com.forgerock.opendj.cli.Utils.*;
030
031import static org.opends.messages.AdminToolMessages.*;
032
033import java.awt.Component;
034import java.awt.GridBagConstraints;
035import java.awt.GridBagLayout;
036import java.awt.Insets;
037import java.awt.Point;
038import java.awt.datatransfer.Transferable;
039import java.awt.datatransfer.UnsupportedFlavorException;
040import java.awt.dnd.DnDConstants;
041import java.awt.dnd.DropTarget;
042import java.awt.dnd.DropTargetDragEvent;
043import java.awt.dnd.DropTargetDropEvent;
044import java.awt.dnd.DropTargetEvent;
045import java.awt.dnd.DropTargetListener;
046import java.awt.event.ActionEvent;
047import java.awt.event.ActionListener;
048import java.io.IOException;
049import java.io.StringReader;
050import java.text.ParseException;
051import java.util.ArrayList;
052import java.util.Collection;
053import java.util.HashMap;
054import java.util.HashSet;
055import java.util.LinkedHashMap;
056import java.util.LinkedHashSet;
057import java.util.List;
058import java.util.Map;
059import java.util.Set;
060import java.util.SortedSet;
061import java.util.TreeSet;
062
063import javax.swing.Box;
064import javax.swing.JButton;
065import javax.swing.JCheckBox;
066import javax.swing.JComponent;
067import javax.swing.JLabel;
068import javax.swing.JPanel;
069import javax.swing.JPasswordField;
070import javax.swing.JScrollPane;
071import javax.swing.JTextArea;
072import javax.swing.JTextField;
073import javax.swing.SwingUtilities;
074import javax.swing.border.EmptyBorder;
075import javax.swing.event.DocumentEvent;
076import javax.swing.event.DocumentListener;
077import javax.swing.text.JTextComponent;
078import javax.swing.tree.TreePath;
079
080import org.forgerock.i18n.LocalizableMessage;
081import org.forgerock.i18n.LocalizableMessageBuilder;
082import org.forgerock.opendj.ldap.ByteString;
083import org.opends.guitools.controlpanel.datamodel.BinaryValue;
084import org.opends.guitools.controlpanel.datamodel.CheckEntrySyntaxException;
085import org.opends.guitools.controlpanel.datamodel.CustomSearchResult;
086import org.opends.guitools.controlpanel.datamodel.ObjectClassValue;
087import org.opends.guitools.controlpanel.event.ScrollPaneBorderListener;
088import org.opends.guitools.controlpanel.task.OnlineUpdateException;
089import org.opends.guitools.controlpanel.ui.components.BinaryCellPanel;
090import org.opends.guitools.controlpanel.ui.components.ObjectClassCellPanel;
091import org.opends.guitools.controlpanel.ui.nodes.BrowserNodeInfo;
092import org.opends.guitools.controlpanel.ui.nodes.DndBrowserNodes;
093import org.opends.guitools.controlpanel.util.Utilities;
094import org.opends.server.schema.SchemaConstants;
095import org.opends.server.types.*;
096import org.opends.server.util.Base64;
097import org.opends.server.util.LDIFReader;
098import org.opends.server.util.ServerConstants;
099
100/**
101 * The panel displaying a simplified view of an entry.
102 */
103public class SimplifiedViewEntryPanel extends ViewEntryPanel
104{
105  private static final long serialVersionUID = 2775960608128921072L;
106  private JPanel attributesPanel;
107  private ScrollPaneBorderListener scrollListener;
108  private GenericDialog binaryDlg;
109  private BinaryValuePanel binaryPanel;
110  private GenericDialog editBinaryDlg;
111  private BinaryAttributeEditorPanel editBinaryPanel;
112  private GenericDialog editOcDlg;
113  private ObjectClassEditorPanel editOcPanel;
114  private JLabel requiredLabel;
115  private JCheckBox showOnlyAttrsWithValues;
116
117  private DropTargetListener dropTargetListener;
118
119  private GenericDialog browseEntriesDlg;
120  private LDAPEntrySelectionPanel browseEntriesPanel;
121
122  private Map<String, List<String>> lastUserPasswords = new HashMap<>();
123
124  private CustomSearchResult searchResult;
125  private boolean isReadOnly;
126  private TreePath treePath;
127  private JScrollPane scrollAttributes;
128
129  private LinkedHashMap<String, List<EditorComponent>> hmEditors = new LinkedHashMap<>();
130
131  private Set<String> requiredAttrs = new HashSet<>();
132  private Map<String, JComponent> hmLabels = new HashMap<>();
133  private Map<String, String> hmDisplayedNames = new HashMap<>();
134  private Map<String, JComponent> hmComponents = new HashMap<>();
135
136  private final String CONFIRM_PASSWORD = "confirm password";
137
138  /** Map containing as key the attribute name and as value a localizable message. */
139  static Map<String, LocalizableMessage> hmFriendlyAttrNames = new HashMap<>();
140  /**
141   * Map containing as key an object class and as value the preferred naming
142   * attribute for the objectclass.
143   */
144  static Map<String, String> hmNameAttrNames = new HashMap<>();
145  static Map<String, String[]> hmOrdereredAttrNames = new HashMap<>();
146  static
147  {
148    hmFriendlyAttrNames.put(ServerConstants.OBJECTCLASS_ATTRIBUTE_TYPE_NAME,
149        INFO_CTRL_PANEL_OBJECTCLASS_FRIENDLY_NAME.get());
150    hmFriendlyAttrNames.put(ServerConstants.ATTR_COMMON_NAME,
151        INFO_CTRL_PANEL_CN_FRIENDLY_NAME.get());
152    hmFriendlyAttrNames.put("givenname",
153        INFO_CTRL_PANEL_GIVENNAME_FRIENDLY_NAME.get());
154    hmFriendlyAttrNames.put("sn",
155        INFO_CTRL_PANEL_SN_FRIENDLY_NAME.get());
156    hmFriendlyAttrNames.put("uid",
157        INFO_CTRL_PANEL_UID_FRIENDLY_NAME.get());
158    hmFriendlyAttrNames.put("employeenumber",
159        INFO_CTRL_PANEL_EMPLOYEENUMBER_FRIENDLY_NAME.get());
160    hmFriendlyAttrNames.put("userpassword",
161        INFO_CTRL_PANEL_USERPASSWORD_FRIENDLY_NAME.get());
162    hmFriendlyAttrNames.put("authpassword",
163        INFO_CTRL_PANEL_AUTHPASSWORD_FRIENDLY_NAME.get());
164    hmFriendlyAttrNames.put("mail",
165        INFO_CTRL_PANEL_MAIL_FRIENDLY_NAME.get());
166    hmFriendlyAttrNames.put("street",
167        INFO_CTRL_PANEL_STREET_FRIENDLY_NAME.get());
168    hmFriendlyAttrNames.put("l",
169        INFO_CTRL_PANEL_L_FRIENDLY_NAME.get());
170    hmFriendlyAttrNames.put("st",
171        INFO_CTRL_PANEL_ST_FRIENDLY_NAME.get());
172    hmFriendlyAttrNames.put("postalcode",
173        INFO_CTRL_PANEL_POSTALCODE_FRIENDLY_NAME.get());
174    hmFriendlyAttrNames.put("mobile",
175        INFO_CTRL_PANEL_MOBILE_FRIENDLY_NAME.get());
176    hmFriendlyAttrNames.put("homephone",
177        INFO_CTRL_PANEL_HOMEPHONE_FRIENDLY_NAME.get());
178    hmFriendlyAttrNames.put("telephonenumber",
179        INFO_CTRL_PANEL_TELEPHONENUMBER_FRIENDLY_NAME.get());
180    hmFriendlyAttrNames.put("pager",
181        INFO_CTRL_PANEL_PAGER_FRIENDLY_NAME.get());
182    hmFriendlyAttrNames.put("facsimiletelephonenumber",
183        INFO_CTRL_PANEL_FACSIMILETELEPHONENUMBER_FRIENDLY_NAME.get());
184    hmFriendlyAttrNames.put("description",
185        INFO_CTRL_PANEL_DESCRIPTION_FRIENDLY_NAME.get());
186    hmFriendlyAttrNames.put("postaladdress",
187        INFO_CTRL_PANEL_POSTALADDRESS_FRIENDLY_NAME.get());
188    hmFriendlyAttrNames.put(ServerConstants.ATTR_UNIQUE_MEMBER_LC,
189        INFO_CTRL_PANEL_UNIQUEMEMBER_FRIENDLY_NAME.get());
190    hmFriendlyAttrNames.put(ServerConstants.ATTR_MEMBER,
191        INFO_CTRL_PANEL_MEMBER_FRIENDLY_NAME.get());
192    hmFriendlyAttrNames.put(ServerConstants.ATTR_MEMBER_URL_LC,
193        INFO_CTRL_PANEL_MEMBERURL_FRIENDLY_NAME.get());
194    hmFriendlyAttrNames.put(ServerConstants.ATTR_C,
195        INFO_CTRL_PANEL_C_FRIENDLY_NAME.get());
196    hmFriendlyAttrNames.put("ds-target-group-dn",
197        INFO_CTRL_PANEL_DS_TARGET_GROUP_DN_FRIENDLY_NAME.get());
198    hmFriendlyAttrNames.put("usercertificate",
199        INFO_CTRL_PANEL_USERCERTIFICATE_FRIENDLY_NAME.get());
200    hmFriendlyAttrNames.put("jpegphoto",
201        INFO_CTRL_PANEL_JPEGPHOTO_FRIENDLY_NAME.get());
202    hmFriendlyAttrNames.put(ServerConstants.ATTR_SUPPORTED_AUTH_PW_SCHEMES_LC,
203        INFO_CTRL_PANEL_SUPPORTEDPWDSCHEMES_FRIENDLY_NAME.get());
204    hmFriendlyAttrNames.put(ServerConstants.ATTR_SUPPORTED_CONTROL_LC,
205        INFO_CTRL_PANEL_SUPPORTEDCONTROLS_FRIENDLY_NAME.get());
206    hmFriendlyAttrNames.put(ServerConstants.ATTR_SUPPORTED_LDAP_VERSION_LC,
207        INFO_CTRL_PANEL_SUPPORTEDLDAPVERSIONS_FRIENDLY_NAME.get());
208    hmFriendlyAttrNames.put(ServerConstants.ATTR_SUPPORTED_CONTROL_LC,
209        INFO_CTRL_PANEL_SUPPORTEDCONTROLS_FRIENDLY_NAME.get());
210    hmFriendlyAttrNames.put(ServerConstants.ATTR_SUPPORTED_EXTENSION_LC,
211        INFO_CTRL_PANEL_SUPPORTEDEXTENSIONS_FRIENDLY_NAME.get());
212    hmFriendlyAttrNames.put(ServerConstants.ATTR_SUPPORTED_FEATURE_LC,
213        INFO_CTRL_PANEL_SUPPORTEDFEATURES_FRIENDLY_NAME.get());
214    hmFriendlyAttrNames.put(ServerConstants.ATTR_VENDOR_NAME_LC,
215        INFO_CTRL_PANEL_VENDORNAME_FRIENDLY_NAME.get());
216    hmFriendlyAttrNames.put(ServerConstants.ATTR_VENDOR_VERSION_LC,
217        INFO_CTRL_PANEL_VENDORVERSION_FRIENDLY_NAME.get());
218    hmFriendlyAttrNames.put(ServerConstants.ATTR_NAMING_CONTEXTS_LC,
219        INFO_CTRL_PANEL_NAMINGCONTEXTS_FRIENDLY_NAME.get());
220    hmFriendlyAttrNames.put(ServerConstants.ATTR_PRIVATE_NAMING_CONTEXTS,
221        INFO_CTRL_PANEL_PRIVATENAMINGCONTEXTS_FRIENDLY_NAME.get());
222
223    hmNameAttrNames.put("organizationalunit", ServerConstants.ATTR_OU);
224    hmNameAttrNames.put("domain", ServerConstants.ATTR_DC);
225    hmNameAttrNames.put("organization", ServerConstants.ATTR_O);
226    hmNameAttrNames.put(ServerConstants.OC_GROUP_OF_URLS_LC,
227        ServerConstants.ATTR_COMMON_NAME);
228    hmNameAttrNames.put(ServerConstants.OC_GROUP_OF_NAMES_LC,
229        ServerConstants.ATTR_COMMON_NAME);
230
231    hmOrdereredAttrNames.put("person",
232        new String[]{"givenname", "sn", ServerConstants.ATTR_COMMON_NAME, "uid",
233        "userpassword", "mail", "telephonenumber", "facsimiletelephonenumber",
234        "employeenumber", "street", "l", "st", "postalcode", "mobile",
235        "homephone", "pager", "description", "postaladdress"});
236    hmOrdereredAttrNames.put(ServerConstants.OC_GROUP_OF_NAMES_LC,
237        new String[]{"cn", "description",
238        ServerConstants.ATTR_UNIQUE_MEMBER_LC, "ds-target-group-dn"});
239    hmOrdereredAttrNames.put(ServerConstants.OC_GROUP_OF_URLS_LC,
240        new String[]{"cn", "description", ServerConstants.ATTR_MEMBER_URL_LC});
241    hmOrdereredAttrNames.put("organizationalunit",
242        new String[]{"ou", "description", "postalAddress", "telephonenumber",
243    "facsimiletelephonenumber"});
244    hmOrdereredAttrNames.put("organization", new String[]{"o", "description"});
245    hmOrdereredAttrNames.put("domain", new String[]{"dc", "description"});
246  }
247
248  private LocalizableMessage NAME = INFO_CTRL_PANEL_NAME_LABEL.get();
249
250  /** Default constructor. */
251  public SimplifiedViewEntryPanel()
252  {
253    super();
254    createLayout();
255  }
256
257  /** {@inheritDoc} */
258  public Component getPreferredFocusComponent()
259  {
260    return null;
261  }
262
263  /** {@inheritDoc} */
264  public boolean requiresBorder()
265  {
266    return false;
267  }
268
269  /**
270   * Creates the layout of the panel (but the contents are not populated here).
271   */
272  private void createLayout()
273  {
274    dropTargetListener = new DropTargetListener()
275    {
276      /** {@inheritDoc} */
277      public void dragEnter(DropTargetDragEvent e)
278      {
279      }
280
281      /** {@inheritDoc} */
282      public void dragExit(DropTargetEvent e)
283      {
284      }
285
286      /** {@inheritDoc} */
287      public void dragOver(DropTargetDragEvent e)
288      {
289      }
290
291      /** {@inheritDoc} */
292      public void dropActionChanged(DropTargetDragEvent e)
293      {
294      }
295
296      /** {@inheritDoc} */
297      public void drop(DropTargetDropEvent e)
298      {
299        try {
300          Transferable tr = e.getTransferable();
301
302          //flavor not supported, reject drop
303          if (!tr.isDataFlavorSupported(DndBrowserNodes.INFO_FLAVOR))
304          {
305            e.rejectDrop();
306          }
307
308          //cast into appropriate data type
309          DndBrowserNodes nodes =
310            (DndBrowserNodes) tr.getTransferData(DndBrowserNodes.INFO_FLAVOR);
311
312          Component comp = e.getDropTargetContext().getComponent();
313          if (comp instanceof JTextArea)
314          {
315            JTextArea ta = (JTextArea)comp;
316            StringBuilder sb = new StringBuilder();
317            sb.append(ta.getText());
318            for (BrowserNodeInfo node : nodes.getNodes())
319            {
320              if (sb.length() > 0)
321              {
322                sb.append("\n");
323              }
324              sb.append(node.getNode().getDN());
325            }
326            ta.setText(sb.toString());
327            ta.setCaretPosition(sb.length());
328          }
329          else if (comp instanceof JTextField)
330          {
331            JTextField tf = (JTextField)comp;
332            if (nodes.getNodes().length > 0)
333            {
334              String dn = nodes.getNodes()[0].getNode().getDN();
335              tf.setText(dn);
336              tf.setCaretPosition(dn.length());
337            }
338          }
339          e.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
340          e.getDropTargetContext().dropComplete(true);
341        }
342        catch (IOException | UnsupportedFlavorException io)
343        {
344          e.rejectDrop();
345        }
346      }
347    };
348
349    GridBagConstraints gbc = new GridBagConstraints();
350    gbc.gridx = 0;
351    gbc.gridy = 0;
352    gbc.gridwidth = 2;
353    gbc.fill = GridBagConstraints.NONE;
354    gbc.anchor = GridBagConstraints.WEST;
355    gbc.weightx = 1.0;
356    gbc.insets = new Insets(10, 10, 0, 10);
357
358    addTitlePanel(this, gbc);
359
360    gbc.gridy ++;
361    gbc.insets = new Insets(5, 10, 5, 10);
362
363    gbc.gridwidth = 1;
364    showOnlyAttrsWithValues =
365      Utilities.createCheckBox(
366          INFO_CTRL_PANEL_SHOW_ATTRS_WITH_VALUES_LABEL.get());
367    showOnlyAttrsWithValues.setSelected(displayOnlyWithAttrs);
368    showOnlyAttrsWithValues.addActionListener(new ActionListener()
369    {
370       /** {@inheritDoc} */
371       public void actionPerformed(ActionEvent ev)
372       {
373         updateAttributeVisibility(!showOnlyAttrsWithValues.isSelected());
374         displayOnlyWithAttrs = showOnlyAttrsWithValues.isSelected();
375       }
376    });
377    gbc.weightx = 0.0;
378    gbc.anchor = GridBagConstraints.WEST;
379    add(showOnlyAttrsWithValues, gbc);
380    gbc.gridx ++;
381    gbc.anchor = GridBagConstraints.EAST;
382    gbc.fill = GridBagConstraints.NONE;
383    requiredLabel = createRequiredLabel();
384    add(requiredLabel, gbc);
385    gbc.insets = new Insets(0, 0, 0, 0);
386    add(Box.createVerticalStrut(10), gbc);
387
388    showOnlyAttrsWithValues.setFont(requiredLabel.getFont());
389
390    attributesPanel = new JPanel(new GridBagLayout());
391    attributesPanel.setOpaque(false);
392    attributesPanel.setBorder(new EmptyBorder(5, 10, 5, 10));
393    gbc.gridx = 0;
394    gbc.weightx = 1.0;
395    gbc.weighty = 1.0;
396    gbc.gridwidth = 2;
397    gbc.gridy ++;
398    gbc.fill = GridBagConstraints.BOTH;
399    scrollAttributes = Utilities.createBorderLessScrollBar(attributesPanel);
400    scrollListener = ScrollPaneBorderListener.createBottomAndTopBorderListener(
401        scrollAttributes);
402    gbc.insets = new Insets(0, 0, 0, 0);
403    add(scrollAttributes, gbc);
404  }
405
406  /** {@inheritDoc} */
407  public void update(CustomSearchResult sr, boolean isReadOnly, TreePath path)
408  {
409    boolean sameEntry = false;
410    if (searchResult != null && sr != null)
411    {
412      sameEntry = searchResult.getDN().equals(sr.getDN());
413    }
414    final Point p = sameEntry ?
415        scrollAttributes.getViewport().getViewPosition() : new Point(0, 0);
416    searchResult = sr;
417    this.isReadOnly = isReadOnly;
418    this.treePath = path;
419
420    updateTitle(sr, path);
421
422    requiredLabel.setVisible(!isReadOnly);
423    showOnlyAttrsWithValues.setVisible(!isReadOnly);
424
425    attributesPanel.removeAll();
426
427    GridBagConstraints gbc = new GridBagConstraints();
428    gbc.fill = GridBagConstraints.HORIZONTAL;
429
430    lastUserPasswords.clear();
431    hmEditors.clear();
432
433    hmLabels.clear();
434    hmDisplayedNames.clear();
435    hmComponents.clear();
436    requiredAttrs.clear();
437
438
439    // Build the attributes panel.
440    Collection<String> sortedAttributes = getSortedAttributes(sr, isReadOnly);
441    if (isReadOnly)
442    {
443      for (String attr : sortedAttributes)
444      {
445        JLabel label = getLabelForAttribute(attr, sr);
446        List<Object> values = sr.getAttributeValues(attr);
447        JComponent comp = getReadOnlyComponent(attr, values);
448        gbc.weightx = 0.0;
449        if (values.size() > 1)
450        {
451          gbc.anchor = GridBagConstraints.NORTHWEST;
452        }
453        else
454        {
455          gbc.anchor = GridBagConstraints.WEST;
456          if (values.size() == 1)
457          {
458            Object v = values.get(0);
459            if (v instanceof String
460                && ((String) v).contains("\n"))
461            {
462              gbc.anchor = GridBagConstraints.NORTHWEST;
463            }
464          }
465        }
466        gbc.insets.left = 0;
467        gbc.gridwidth = GridBagConstraints.RELATIVE;
468        attributesPanel.add(label, gbc);
469        gbc.insets.left = 10;
470        gbc.weightx = 1.0;
471        gbc.gridwidth = GridBagConstraints.REMAINDER;
472        attributesPanel.add(comp, gbc);
473        gbc.insets.top = 10;
474      }
475    }
476    else
477    {
478      for (String attr : sortedAttributes)
479      {
480        JLabel label = getLabelForAttribute(attr, sr);
481        if (isRequired(attr, sr))
482        {
483          Utilities.setRequiredIcon(label);
484          requiredAttrs.add(attr.toLowerCase());
485        }
486        List<Object> values = sr.getAttributeValues(attr);
487        if (values.isEmpty())
488        {
489          values = new ArrayList<>(1);
490          if (isBinary(attr))
491          {
492            values.add(new byte[]{});
493          }
494          else
495          {
496            values.add("");
497          }
498        }
499
500        if (isPassword(attr))
501        {
502          List<String> pwds = new ArrayList<>();
503          for (Object o : values)
504          {
505            pwds.add(getPasswordStringValue(o));
506          }
507          lastUserPasswords.put(attr.toLowerCase(), pwds);
508        }
509
510        JComponent comp = getReadWriteComponent(attr, values);
511
512        gbc.weightx = 0.0;
513        if (attr.equalsIgnoreCase(
514            ServerConstants.OBJECTCLASS_ATTRIBUTE_TYPE_NAME))
515        {
516          int nOcs = 0;
517          for (Object o : values)
518          {
519            if (!"top".equals(o))
520            {
521              nOcs ++;
522            }
523          }
524          if (nOcs > 1)
525          {
526            gbc.anchor = GridBagConstraints.NORTHWEST;
527          }
528          else
529          {
530            gbc.anchor = GridBagConstraints.WEST;
531          }
532        }
533        else if (isSingleValue(attr))
534        {
535          gbc.anchor = GridBagConstraints.WEST;
536        }
537        else if (values.size() <= 1 &&
538                (hasBinaryValue(values) || isBinary(attr)))
539        {
540          gbc.anchor = GridBagConstraints.WEST;
541        }
542        else
543        {
544          gbc.anchor = GridBagConstraints.NORTHWEST;
545        }
546        gbc.insets.left = 0;
547        gbc.gridwidth = GridBagConstraints.RELATIVE;
548        attributesPanel.add(label, gbc);
549        gbc.insets.left = 10;
550        gbc.weightx = 1.0;
551        gbc.gridwidth = GridBagConstraints.REMAINDER;
552        attributesPanel.add(comp, gbc);
553        gbc.insets.top = 10;
554        hmLabels.put(attr.toLowerCase(), label);
555        hmComponents.put(attr.toLowerCase(), comp);
556
557        if (isPassword(attr))
558        {
559          label = Utilities.createPrimaryLabel(
560              INFO_CTRL_PANEL_PASSWORD_CONFIRM_LABEL.get());
561          String key = getConfirmPasswordKey(attr);
562          comp = getReadWriteComponent(key, values);
563
564          hmLabels.put(key, label);
565          hmComponents.put(key, comp);
566
567          gbc.weightx = 0.0;
568          if (isSingleValue(attr))
569          {
570            gbc.anchor = GridBagConstraints.WEST;
571          }
572          else
573          {
574            gbc.anchor = GridBagConstraints.NORTHWEST;
575          }
576          gbc.insets.left = 0;
577          gbc.gridwidth = GridBagConstraints.RELATIVE;
578          attributesPanel.add(label, gbc);
579          gbc.insets.left = 10;
580          gbc.weightx = 1.0;
581          gbc.gridwidth = GridBagConstraints.REMAINDER;
582          attributesPanel.add(comp, gbc);
583          gbc.insets.top = 10;
584        }
585      }
586    }
587    gbc.weighty = 1.0;
588    gbc.gridwidth = GridBagConstraints.REMAINDER;
589    gbc.fill = GridBagConstraints.VERTICAL;
590    gbc.insets = new Insets(0, 0, 0, 0);
591    attributesPanel.add(Box.createVerticalGlue(), gbc);
592    scrollListener.updateBorder();
593
594    if (showOnlyAttrsWithValues.isSelected())
595    {
596      updateAttributeVisibility(false);
597    }
598    else if (isVisible())
599    {
600      repaint();
601    }
602
603    SwingUtilities.invokeLater(new Runnable()
604    {
605      /** {@inheritDoc} */
606      public void run()
607      {
608        if (p != null && scrollAttributes.getViewport().contains(p))
609        {
610          scrollAttributes.getViewport().setViewPosition(p);
611        }
612        ignoreEntryChangeEvents = false;
613      }
614    });
615  }
616
617  private JLabel getLabelForAttribute(String attrName, CustomSearchResult sr)
618  {
619    LocalizableMessageBuilder l = new LocalizableMessageBuilder();
620    int index = attrName.indexOf(";");
621    String basicAttrName;
622    String subType;
623    if (index == -1)
624    {
625      basicAttrName = attrName;
626      subType = null;
627    }
628    else
629    {
630      basicAttrName = attrName.substring(0, index);
631      subType = attrName.substring(index + 1);
632    }
633    if (subType != null && subType.equalsIgnoreCase("binary"))
634    {
635      // TODO: use message
636      subType = "binary";
637    }
638    boolean isNameAttribute = isAttrName(basicAttrName, sr);
639    if (isNameAttribute)
640    {
641      if (subType != null)
642      {
643        l.append(NAME).append(" (").append(subType).append(")");
644      }
645      else
646      {
647        l.append(NAME);
648      }
649    }
650    else
651    {
652      LocalizableMessage friendly = hmFriendlyAttrNames.get(basicAttrName.toLowerCase());
653      if (friendly == null)
654      {
655        l.append(attrName);
656      }
657      else
658      {
659        l.append(friendly);
660        if (subType != null)
661        {
662          l.append(" (").append(subType).append(")");
663        }
664      }
665    }
666    hmDisplayedNames.put(attrName.toLowerCase(), l.toString());
667    l.append(":");
668    return Utilities.createPrimaryLabel(l.toMessage());
669  }
670
671  private Collection<String> getSortedAttributes(CustomSearchResult sr,
672      boolean isReadOnly)
673  {
674    LinkedHashSet<String> attrNames = new LinkedHashSet<>();
675
676//  Get all attributes that the entry can have
677    Set<String> attributes = new LinkedHashSet<>();
678    ArrayList<String> entryAttrs = new ArrayList<>(sr.getAttributeNames());
679    ArrayList<String> attrsWithNoOptions = new ArrayList<>();
680    for (String attr : entryAttrs)
681    {
682      attrsWithNoOptions.add(
683            Utilities.getAttributeNameWithoutOptions(attr).toLowerCase());
684    }
685
686    List<Object> values =
687      sr.getAttributeValues(ServerConstants.OBJECTCLASS_ATTRIBUTE_TYPE_NAME);
688
689    // Put first the attributes associated with the objectclass in
690    // hmOrderedAttrNames
691    for (Object o : values)
692    {
693      String ocName = (String)o;
694      String[] attrs = hmOrdereredAttrNames.get(ocName.toLowerCase());
695      if (attrs != null)
696      {
697        for (String attr : attrs)
698        {
699          int index = attrsWithNoOptions.indexOf(attr.toLowerCase());
700          if (index != -1)
701          {
702            attrNames.add(entryAttrs.get(index));
703          }
704          else
705          {
706            attrNames.add(attr);
707          }
708        }
709      }
710    }
711    // Handle the root entry separately: most of its attributes are operational
712    // so we filter a list of harcoded attributes.
713    boolean isRootEntry = sr.getDN().equals("");
714    Schema schema = getInfo().getServerDescriptor().getSchema();
715    if (isRootEntry)
716    {
717      String[] attrsNotToAdd = {"entryuuid", "hassubordinates",
718          "numsubordinates", "subschemasubentry", "entrydn",
719      "hassubordinates"};
720      for (String attr : sr.getAttributeNames())
721      {
722        boolean found = false;
723        for (String addedAttr : attrNames)
724        {
725          found = addedAttr.equalsIgnoreCase(attr);
726          if (found)
727          {
728            break;
729          }
730        }
731        if (!found)
732        {
733          for (String notToAddAttr : attrsNotToAdd)
734          {
735            found = notToAddAttr.equalsIgnoreCase(attr);
736            if (found)
737            {
738              break;
739            }
740          }
741        }
742        if (!found)
743        {
744          attrNames.add(attr);
745        }
746      }
747    }
748    else
749    {
750      // Try to get the attributes from the schema: first display the required
751      // attributes with a friendly name (in alphabetical order), then (in
752      // alphabetical order) the attributes with no friendly name.  Finally
753      // do the same with the other attributes.
754
755      SortedSet<String> requiredAttributes = new TreeSet<>();
756      SortedSet<String> allowedAttributes = new TreeSet<>();
757
758      if (schema != null)
759      {
760        List<Object> ocs = sr.getAttributeValues(
761            ServerConstants.OBJECTCLASS_ATTRIBUTE_TYPE_NAME);
762        for (Object o : ocs)
763        {
764          String oc = (String)o;
765          ObjectClass objectClass = schema.getObjectClass(oc.toLowerCase());
766          if (objectClass != null)
767          {
768            for (AttributeType attr : objectClass.getRequiredAttributeChain())
769            {
770              requiredAttributes.add(attr.getNameOrOID());
771            }
772            for (AttributeType attr : objectClass.getOptionalAttributeChain())
773            {
774              allowedAttributes.add(attr.getNameOrOID());
775            }
776          }
777        }
778      }
779      // Now try to put first the attributes for which we have a friendly
780      // name (the most common ones).
781      updateAttributes(attributes, requiredAttributes, entryAttrs,
782          attrsWithNoOptions, true);
783      updateAttributes(attributes, requiredAttributes, entryAttrs,
784          attrsWithNoOptions, false);
785      updateAttributes(attributes, allowedAttributes, entryAttrs,
786          attrsWithNoOptions, true);
787      updateAttributes(attributes, allowedAttributes, entryAttrs,
788          attrsWithNoOptions, false);
789
790
791      attributes.addAll(entryAttrs);
792      attributes.add("aci");
793
794      // In read-only mode display only the attributes with values
795      if (isReadOnly)
796      {
797        attributes.retainAll(entryAttrs);
798      }
799
800      for (String attr : attributes)
801      {
802        boolean add = isEditable(attr, schema);
803
804        if (add)
805        {
806          boolean found = false;
807          for (String addedAttr : attrNames)
808          {
809            found = addedAttr.equalsIgnoreCase(attr);
810            if (found)
811            {
812              break;
813            }
814          }
815          if (!found)
816          {
817            attrNames.add(attr);
818          }
819        }
820      }
821    }
822    return attrNames;
823  }
824
825  private void updateAttributes(
826      Collection<String> attributes,
827      Set<String> newAttributes,
828      ArrayList<String> entryAttrs,
829      ArrayList<String> attrsWithNoOptions,
830      boolean addIfFriendlyName)
831  {
832    for (String attr : newAttributes)
833    {
834      String attrLc = attr.toLowerCase();
835      boolean hasFriendlyName = hmFriendlyAttrNames.get(attrLc) != null;
836      if (hasFriendlyName == addIfFriendlyName)
837      {
838        int index = attrsWithNoOptions.indexOf(attrLc);
839        if (index != -1)
840        {
841          attributes.add(entryAttrs.get(index));
842        }
843        else
844        {
845          if (!hasCertificateSyntax(attr,
846              getInfo().getServerDescriptor().getSchema()))
847          {
848            attributes.add(attr);
849          }
850          else
851          {
852            attributes.add(attr+";binary");
853          }
854        }
855      }
856    }
857  }
858
859  private JComponent getReadOnlyComponent(final String attrName,
860      List<Object> values)
861  {
862//  GridLayout is used to avoid the 512 limit of GridBagLayout
863    JPanel panel = new JPanel(new GridBagLayout());
864    panel.setOpaque(false);
865    GridBagConstraints gbc = new GridBagConstraints();
866    gbc.gridy = 0;
867
868    boolean isBinary = hasBinaryValue(values);
869    for (Object o : values)
870    {
871      gbc.fill = GridBagConstraints.HORIZONTAL;
872      gbc.weightx = 1.0;
873      gbc.gridx = 0;
874
875      if (attrName.equalsIgnoreCase(
876          ServerConstants.OBJECTCLASS_ATTRIBUTE_TYPE_NAME))
877      {
878        ObjectClassCellPanel ocPanel = new ObjectClassCellPanel();
879        Schema schema = getInfo().getServerDescriptor().getSchema();
880        if (schema != null)
881        {
882          ObjectClassValue ocDescriptor = getObjectClassDescriptor(values,
883              schema);
884          ocPanel.setValue(ocDescriptor);
885        }
886        ocPanel.setEditButtonVisible(false);
887        panel.add(ocPanel, gbc);
888        break;
889      }
890      else if (Utilities.mustObfuscate(attrName,
891          getInfo().getServerDescriptor().getSchema()))
892      {
893        panel.add(
894            Utilities.createDefaultLabel(
895                LocalizableMessage.raw(OBFUSCATED_VALUE)), gbc);
896      }
897      else if (!isBinary)
898      {
899        Set<String> sValues = toStrings(values);
900        LocalizableMessage text = LocalizableMessage.raw(Utilities.getStringFromCollection(sValues, "\n"));
901        final JTextArea ta;
902        JComponent toAdd;
903        if (values.size() > 15)
904        {
905          ta = Utilities.createNonEditableTextArea(text, 15, 20);
906          toAdd = Utilities.createScrollPane(ta);
907        }
908        else
909        {
910          ta = Utilities.createNonEditableTextArea(text, values.size(), 20);
911          toAdd = ta;
912        }
913        panel.add(toAdd, gbc);
914        break;
915      }
916      else
917      {
918        final BinaryCellPanel pane = new BinaryCellPanel();
919        pane.setEditButtonText(INFO_CTRL_PANEL_VIEW_BUTTON_LABEL.get());
920        final byte[] binaryValue = (byte[])o;
921        Schema schema = getInfo().getServerDescriptor().getSchema();
922        final boolean isImage = Utilities.hasImageSyntax(attrName, schema);
923        pane.setValue(binaryValue, isImage);
924        pane.addEditActionListener(new ActionListener()
925        {
926          /** {@inheritDoc} */
927          public void actionPerformed(ActionEvent ev)
928          {
929            if (binaryDlg == null)
930            {
931              binaryPanel = new BinaryValuePanel();
932              binaryPanel.setInfo(getInfo());
933              binaryDlg = new GenericDialog(
934                  Utilities.getFrame(SimplifiedViewEntryPanel.this),
935                  binaryPanel);
936              binaryDlg.setModal(true);
937              Utilities.centerGoldenMean(binaryDlg,
938                  Utilities.getParentDialog(SimplifiedViewEntryPanel.this));
939            }
940            binaryPanel.setValue(attrName, binaryValue);
941            binaryDlg.setVisible(true);
942          }
943        });
944        panel.add(pane, gbc);
945      }
946    }
947    return panel;
948  }
949
950  private Set<String> toStrings(Collection<Object> objects)
951  {
952    Set<String> results = new TreeSet<>();
953    for (Object o : objects)
954    {
955      results.add(String.valueOf(o));
956    }
957    return results;
958  }
959
960  private JComponent getReadWriteComponent(final String attrName,
961      List<Object> values)
962  {
963    JPanel panel = new JPanel(new GridBagLayout());
964    panel.setOpaque(false);
965    GridBagConstraints gbc = new GridBagConstraints();
966    gbc.gridy = 0;
967
968    List<EditorComponent> components = new ArrayList<>();
969    hmEditors.put(attrName.toLowerCase(), components);
970
971    boolean isBinary = hasBinaryValue(values);
972    for (Object o : values)
973    {
974      gbc.fill = GridBagConstraints.HORIZONTAL;
975      gbc.weightx = 1.0;
976      gbc.gridx = 0;
977      if (attrName.equalsIgnoreCase(
978          ServerConstants.OBJECTCLASS_ATTRIBUTE_TYPE_NAME))
979      {
980        final ObjectClassCellPanel ocCellPanel = new ObjectClassCellPanel();
981        Schema schema = getInfo().getServerDescriptor().getSchema();
982        final ObjectClassValue ocDescriptor;
983        if (schema != null)
984        {
985          ocDescriptor = getObjectClassDescriptor(values, schema);
986          ocCellPanel.setValue(ocDescriptor);
987        }
988        else
989        {
990          ocDescriptor = null;
991        }
992        ocCellPanel.addEditActionListener(new ActionListener()
993        {
994          private ObjectClassValue newValue;
995          /** {@inheritDoc} */
996          public void actionPerformed(ActionEvent ev)
997          {
998            if (editOcDlg == null)
999            {
1000              editOcPanel = new ObjectClassEditorPanel();
1001              editOcPanel.setInfo(getInfo());
1002              editOcDlg = new GenericDialog(
1003                  null,
1004                  editOcPanel);
1005              editOcDlg.setModal(true);
1006              Utilities.centerGoldenMean(editOcDlg,
1007                  Utilities.getParentDialog(SimplifiedViewEntryPanel.this));
1008            }
1009            if (newValue == null && ocDescriptor != null)
1010            {
1011              editOcPanel.setValue(ocDescriptor);
1012            }
1013            else
1014            {
1015              editOcPanel.setValue(newValue);
1016            }
1017            editOcDlg.setVisible(true);
1018            if (editOcPanel.valueChanged())
1019            {
1020              newValue = editOcPanel.getObjectClassValue();
1021              ocCellPanel.setValue(newValue);
1022              updatePanel(newValue);
1023            }
1024          }
1025        });
1026        panel = ocCellPanel;
1027        components.add(new EditorComponent(ocCellPanel));
1028        break;
1029      }
1030      else if (isPassword(attrName) || isConfirmPassword(attrName))
1031      {
1032        JPasswordField pf = Utilities.createPasswordField();
1033        if (!o.equals(""))
1034        {
1035          pf.setText(getPasswordStringValue(o));
1036        }
1037        panel.add(pf, gbc);
1038        components.add(new EditorComponent(pf));
1039      }
1040      else if (!isBinary)
1041      {
1042        if (isSingleValue(attrName))
1043        {
1044          final JTextField tf = Utilities.createMediumTextField();
1045          tf.setText(String.valueOf(o));
1046          gbc.gridx = 0;
1047          panel.add(tf, gbc);
1048          if (mustAddBrowseButton(attrName))
1049          {
1050            gbc.insets.left = 5;
1051            gbc.weightx = 0.0;
1052            gbc.gridx ++;
1053            gbc.anchor = GridBagConstraints.NORTH;
1054            JButton browse = Utilities.createButton(INFO_CTRL_PANEL_BROWSE_BUTTON_LABEL.get());
1055            browse.addActionListener(new AddBrowseClickedActionListener(tf, attrName));
1056            panel.add(browse, gbc);
1057            new DropTarget(tf, dropTargetListener);
1058          }
1059          components.add(new EditorComponent(tf));
1060        }
1061        else
1062        {
1063          Set<String> sValues = toStrings(values);
1064          final LocalizableMessage text = LocalizableMessage.raw(Utilities.getStringFromCollection(sValues, "\n"));
1065          final JTextArea ta;
1066          JComponent toAdd;
1067          if (values.size() > 15)
1068          {
1069            ta = Utilities.createTextArea(text, 15, 20);
1070            toAdd = Utilities.createScrollPane(ta);
1071          }
1072          else
1073          {
1074            ta = Utilities.createTextAreaWithBorder(text, values.size(), 20);
1075            toAdd = ta;
1076          }
1077          panel.add(toAdd, gbc);
1078          if (mustAddBrowseButton(attrName))
1079          {
1080            gbc.insets.left = 5;
1081            gbc.weightx = 0.0;
1082            gbc.gridx ++;
1083            gbc.anchor = GridBagConstraints.NORTH;
1084            final JButton browse = Utilities.createButton(
1085                INFO_CTRL_PANEL_BROWSE_BUTTON_LABEL.get());
1086            browse.addActionListener(new AddBrowseClickedActionListener(ta, attrName));
1087            if (attrName.equalsIgnoreCase(ServerConstants.ATTR_UNIQUE_MEMBER_LC))
1088            {
1089              browse.setText(
1090                  INFO_CTRL_PANEL_ADD_MEMBERS_BUTTON.get().toString());
1091            }
1092            panel.add(browse, gbc);
1093            new DropTarget(ta, dropTargetListener);
1094          }
1095          components.add(new EditorComponent(ta));
1096        }
1097        break;
1098      }
1099      else
1100      {
1101        final BinaryCellPanel pane = new BinaryCellPanel();
1102        Schema schema = getInfo().getServerDescriptor().getSchema();
1103        final boolean isImage = Utilities.hasImageSyntax(attrName, schema);
1104        pane.setDisplayDelete(true);
1105        final byte[] binaryValue = (byte[])o;
1106        if (binaryValue.length > 0)
1107        {
1108          pane.setValue(binaryValue, isImage);
1109        }
1110        pane.addEditActionListener(new ActionListener()
1111        {
1112          private BinaryValue newValue;
1113          /** {@inheritDoc} */
1114          public void actionPerformed(ActionEvent ev)
1115          {
1116            if (editBinaryDlg == null)
1117            {
1118              editBinaryPanel = new BinaryAttributeEditorPanel();
1119              editBinaryPanel.setInfo(getInfo());
1120              editBinaryDlg = new GenericDialog(
1121                  Utilities.getFrame(SimplifiedViewEntryPanel.this),
1122                  editBinaryPanel);
1123              editBinaryDlg.setModal(true);
1124              Utilities.centerGoldenMean(editBinaryDlg,
1125                  Utilities.getParentDialog(SimplifiedViewEntryPanel.this));
1126            }
1127            if (newValue == null)
1128            {
1129              // We use an empty binary array to not breaking the logic:
1130              // it means that there is no value for the attribute.
1131              if (binaryValue != null && binaryValue.length > 0)
1132              {
1133                newValue = BinaryValue.createBase64(binaryValue);
1134                editBinaryPanel.setValue(attrName, newValue);
1135              }
1136              else
1137              {
1138                editBinaryPanel.setValue(attrName, null);
1139              }
1140            }
1141            else
1142            {
1143              editBinaryPanel.setValue(attrName, newValue);
1144            }
1145            editBinaryDlg.setVisible(true);
1146            if (editBinaryPanel.valueChanged())
1147            {
1148              newValue = editBinaryPanel.getBinaryValue();
1149              pane.setValue(newValue, isImage);
1150              notifyListeners();
1151            }
1152          }
1153        });
1154        pane.addDeleteActionListener(new ActionListener()
1155        {
1156          /** {@inheritDoc} */
1157          public void actionPerformed(ActionEvent ev)
1158          {
1159            pane.setValue((byte[])null, false);
1160            if (editBinaryPanel != null)
1161            {
1162              editBinaryPanel.setValue(attrName, null);
1163            }
1164            notifyListeners();
1165          }
1166        });
1167        panel.add(pane, gbc);
1168        components.add(new EditorComponent(pane));
1169      }
1170      gbc.gridy ++;
1171      gbc.insets.top = 10;
1172    }
1173    return panel;
1174  }
1175
1176  private boolean isSingleValue(String attrName)
1177  {
1178    boolean isSingleValue = false;
1179
1180    Schema schema = getInfo().getServerDescriptor().getSchema();
1181    if (schema != null)
1182    {
1183      AttributeType attr = schema.getAttributeType(attrName.toLowerCase());
1184      if (attr != null)
1185      {
1186        isSingleValue = attr.isSingleValue();
1187      }
1188    }
1189
1190    return isSingleValue;
1191  }
1192
1193  private boolean isRequired(String attrName, CustomSearchResult sr)
1194  {
1195    boolean isRequired = false;
1196
1197    attrName = Utilities.getAttributeNameWithoutOptions(attrName);
1198
1199    Schema schema = getInfo().getServerDescriptor().getSchema();
1200    if (schema != null)
1201    {
1202      AttributeType attr = schema.getAttributeType(attrName.toLowerCase());
1203      if (attr != null)
1204      {
1205        List<Object> ocs = sr.getAttributeValues(
1206            ServerConstants.OBJECTCLASS_ATTRIBUTE_TYPE_NAME);
1207        for (Object o : ocs)
1208        {
1209          String oc = (String)o;
1210          ObjectClass objectClass = schema.getObjectClass(oc.toLowerCase());
1211          if (objectClass != null && objectClass.isRequired(attr))
1212          {
1213            isRequired = true;
1214            break;
1215          }
1216        }
1217      }
1218    }
1219    return isRequired;
1220  }
1221
1222  /** {@inheritDoc} */
1223  public GenericDialog.ButtonType getButtonType()
1224  {
1225    return GenericDialog.ButtonType.NO_BUTTON;
1226  }
1227
1228  /** {@inheritDoc} */
1229  public Entry getEntry() throws OpenDsException
1230  {
1231    Entry entry = null;
1232
1233    ArrayList<LocalizableMessage> errors = new ArrayList<>();
1234
1235    try
1236    {
1237      DN.valueOf(getDisplayedDN());
1238    }
1239    catch (Throwable t)
1240    {
1241      errors.add(ERR_CTRL_PANEL_DN_NOT_VALID.get());
1242    }
1243
1244    for (String attrName : hmLabels.keySet())
1245    {
1246      setPrimaryValid(hmLabels.get(attrName));
1247    }
1248
1249
1250    // Check passwords
1251    for (String attrName : lastUserPasswords.keySet())
1252    {
1253      List<String> pwds = getNewPasswords(attrName);
1254      List<String> confirmPwds = getConfirmPasswords(attrName);
1255      if (!pwds.equals(confirmPwds))
1256      {
1257        setPrimaryInvalid(hmLabels.get(attrName));
1258        setPrimaryInvalid(hmLabels.get(getConfirmPasswordKey(attrName)));
1259        LocalizableMessage msg = ERR_CTRL_PANEL_PASSWORD_DO_NOT_MATCH.get();
1260        if (!errors.contains(msg))
1261        {
1262          errors.add(msg);
1263        }
1264      }
1265    }
1266    for (String attrName : requiredAttrs)
1267    {
1268      if (!hasValue(attrName))
1269      {
1270        setPrimaryInvalid(hmLabels.get(attrName));
1271        errors.add(ERR_CTRL_PANEL_ATTRIBUTE_REQUIRED.get(
1272            hmDisplayedNames.get(attrName)));
1273      }
1274    }
1275
1276    if (!errors.isEmpty())
1277    {
1278      throw new CheckEntrySyntaxException(errors);
1279    }
1280
1281    LDIFImportConfig ldifImportConfig = null;
1282    try
1283    {
1284      String ldif = getLDIF();
1285
1286      ldifImportConfig = new LDIFImportConfig(new StringReader(ldif));
1287      LDIFReader reader = new LDIFReader(ldifImportConfig);
1288      entry = reader.readEntry(checkSchema());
1289      addValuesInRDN(entry);
1290    }
1291    catch (IOException ioe)
1292    {
1293      throw new OnlineUpdateException(
1294          ERR_CTRL_PANEL_ERROR_CHECKING_ENTRY.get(ioe), ioe);
1295    }
1296    finally
1297    {
1298      if (ldifImportConfig != null)
1299      {
1300        ldifImportConfig.close();
1301      }
1302    }
1303    return entry;
1304  }
1305
1306  private List<String> getDisplayedStringValues(String attrName)
1307  {
1308    List<String> values = new ArrayList<>();
1309    List<EditorComponent> comps =
1310      hmEditors.get(attrName.toLowerCase());
1311    if (comps != null)
1312    {
1313      for (EditorComponent comp : comps)
1314      {
1315        Object value = comp.getValue();
1316        if (value instanceof ObjectClassValue)
1317        {
1318          ObjectClassValue ocValue = (ObjectClassValue)value;
1319          if (ocValue.getStructural() != null)
1320          {
1321            values.add(ocValue.getStructural());
1322          }
1323          values.addAll(ocValue.getAuxiliary());
1324        }
1325        else if (value instanceof Collection<?>)
1326        {
1327          for (Object o : (Collection<?>)value)
1328          {
1329            values.add((String)o);
1330          }
1331        }
1332        else
1333        {
1334          values.add(String.valueOf(comp.getValue()));
1335        }
1336      }
1337    }
1338    return values;
1339  }
1340
1341  private List<String> getNewPasswords(String attrName)
1342  {
1343    String attr =
1344      Utilities.getAttributeNameWithoutOptions(attrName).toLowerCase();
1345    return getDisplayedStringValues(attr);
1346  }
1347
1348  private List<String> getConfirmPasswords(String attrName)
1349  {
1350    return getDisplayedStringValues(getConfirmPasswordKey(attrName));
1351  }
1352
1353  private String getConfirmPasswordKey(String attrName)
1354  {
1355    return CONFIRM_PASSWORD+
1356    Utilities.getAttributeNameWithoutOptions(attrName).toLowerCase();
1357  }
1358
1359  private boolean isConfirmPassword(String key)
1360  {
1361    return key.startsWith(CONFIRM_PASSWORD);
1362  }
1363
1364  /**
1365   * Returns the LDIF representation of the displayed entry.
1366   * @return the LDIF representation of the displayed entry.
1367   */
1368  private String getLDIF()
1369  {
1370    StringBuilder sb = new StringBuilder();
1371    sb.append("dn: ").append(getDisplayedDN());
1372
1373    for (String attrName : hmEditors.keySet())
1374    {
1375      if (isConfirmPassword(attrName))
1376      {
1377        continue;
1378      }
1379      else if (isPassword(attrName))
1380      {
1381        List<String> newPwds = getNewPasswords(attrName);
1382        if (newPwds.equals(lastUserPasswords.get(attrName.toLowerCase())))
1383        {
1384          List<Object> oldValues = searchResult.getAttributeValues(attrName);
1385          if (!oldValues.isEmpty())
1386          {
1387            appendLDIFLines(sb, attrName, oldValues);
1388          }
1389        }
1390        else
1391        {
1392          appendLDIFLines(sb, attrName);
1393        }
1394      }
1395      else
1396        if (!schemaReadOnlyAttributesLowerCase.contains(attrName.toLowerCase()))
1397        {
1398          appendLDIFLines(sb, attrName);
1399        }
1400    }
1401
1402    // Add the attributes that are not displayed
1403    for (String attrName : schemaReadOnlyAttributesLowerCase)
1404    {
1405      List<Object> values = searchResult.getAttributeValues(attrName);
1406      if (!values.isEmpty())
1407      {
1408        appendLDIFLines(sb, attrName, values);
1409      }
1410    }
1411    return sb.toString();
1412  }
1413
1414  private boolean isAttrName(String attrName, CustomSearchResult sr)
1415  {
1416    List<Object> values = sr.getAttributeValues(ServerConstants.OBJECTCLASS_ATTRIBUTE_TYPE_NAME);
1417    for (Object o : values)
1418    {
1419      String ocName = (String)o;
1420      String attr = hmNameAttrNames.get(ocName.toLowerCase());
1421      if (attr != null && attr.equalsIgnoreCase(attrName))
1422      {
1423        return true;
1424      }
1425    }
1426    return false;
1427  }
1428
1429  private boolean hasBinaryValue(List<Object> values)
1430  {
1431    if (!values.isEmpty())
1432    {
1433      return values.iterator().next() instanceof byte[];
1434    }
1435    return false;
1436  }
1437
1438  private boolean mustAddBrowseButton(String attrName)
1439  {
1440    boolean mustAddBrowseButton =
1441      attrName.equalsIgnoreCase(ServerConstants.ATTR_UNIQUE_MEMBER_LC) ||
1442      attrName.equalsIgnoreCase("ds-target-group-dn");
1443    if (!mustAddBrowseButton)
1444    {
1445      Schema schema = getInfo().getServerDescriptor().getSchema();
1446      if (schema != null)
1447      {
1448        AttributeType attr = schema.getAttributeType(attrName.toLowerCase());
1449        if (attr != null)
1450        {
1451          // There is no name for a regex syntax.
1452          String syntaxName = attr.getSyntax().getName();
1453          if (syntaxName!=null) {
1454              mustAddBrowseButton=syntaxName.equalsIgnoreCase(
1455                SchemaConstants.SYNTAX_DN_NAME);
1456          }
1457        }
1458      }
1459    }
1460    return mustAddBrowseButton;
1461  }
1462
1463  /** {@inheritDoc} */
1464  protected List<Object> getValues(String attrName)
1465  {
1466    List<Object> values = new ArrayList<>();
1467    for (EditorComponent comp : hmEditors.get(attrName))
1468    {
1469      if (hasValue(comp))
1470      {
1471        Object value = comp.getValue();
1472        if (value instanceof Collection<?>)
1473        {
1474          values.addAll((Collection<?>) value);
1475        }
1476        else
1477        {
1478          values.add(value);
1479        }
1480      }
1481    }
1482    return values;
1483  }
1484
1485  private void appendLDIFLines(StringBuilder sb, String attrName)
1486  {
1487    List<Object> values = getValues(attrName);
1488    if (!values.isEmpty())
1489    {
1490      appendLDIFLines(sb, attrName, values);
1491    }
1492  }
1493
1494  /** {@inheritDoc} */
1495  protected String getDisplayedDN()
1496  {
1497    StringBuilder sb = new StringBuilder();
1498    try
1499    {
1500      DN oldDN = DN.valueOf(searchResult.getDN());
1501      if (oldDN.size() > 0)
1502      {
1503        RDN rdn = oldDN.rdn();
1504        List<AttributeType> attributeTypes = new ArrayList<>();
1505        List<String> attributeNames = new ArrayList<>();
1506        List<ByteString> attributeValues = new ArrayList<>();
1507        for (int i=0; i<rdn.getNumValues(); i++)
1508        {
1509          String attrName = rdn.getAttributeName(i);
1510          ByteString value = rdn.getAttributeValue(i);
1511
1512          List<String> values = getDisplayedStringValues(attrName);
1513          if (!values.contains(value.toString()))
1514          {
1515            if (!values.isEmpty())
1516            {
1517              String firstNonEmpty = getFirstNonEmpty(values);
1518              if (firstNonEmpty != null)
1519              {
1520                AttributeType attr = rdn.getAttributeType(i);
1521                attributeTypes.add(attr);
1522                attributeNames.add(rdn.getAttributeName(i));
1523                attributeValues.add(ByteString.valueOfUtf8(firstNonEmpty));
1524              }
1525            }
1526          }
1527          else
1528          {
1529            attributeTypes.add(rdn.getAttributeType(i));
1530            attributeNames.add(rdn.getAttributeName(i));
1531            attributeValues.add(value);
1532          }
1533        }
1534        if (attributeTypes.isEmpty())
1535        {
1536          // Check the attributes in the order that we display them and use
1537          // the first one.
1538          Schema schema = getInfo().getServerDescriptor().getSchema();
1539          if (schema != null)
1540          {
1541            for (String attrName : hmEditors.keySet())
1542            {
1543              if (isPassword(attrName) ||
1544                  isConfirmPassword(attrName))
1545              {
1546                continue;
1547              }
1548              List<EditorComponent> comps = hmEditors.get(attrName);
1549              if (!comps.isEmpty())
1550              {
1551                Object o = comps.iterator().next().getValue();
1552                if (o instanceof String)
1553                {
1554                  String aName =
1555                    Utilities.getAttributeNameWithoutOptions(attrName);
1556                  AttributeType attr =
1557                    schema.getAttributeType(aName.toLowerCase());
1558                  if (attr != null)
1559                  {
1560                    attributeTypes.add(attr);
1561                    attributeNames.add(attrName);
1562                    attributeValues.add(ByteString.valueOfUtf8((String) o));
1563                  }
1564                  break;
1565                }
1566              }
1567            }
1568          }
1569        }
1570        DN parent = oldDN.parent();
1571        if (!attributeTypes.isEmpty())
1572        {
1573          RDN newRDN = new RDN(attributeTypes, attributeNames, attributeValues);
1574
1575          DN newDN;
1576          if (parent == null)
1577          {
1578            newDN = new DN(new RDN[]{newRDN});
1579          }
1580          else
1581          {
1582            newDN = parent.child(newRDN);
1583          }
1584          sb.append(newDN);
1585        }
1586        else
1587        {
1588          if (parent != null)
1589          {
1590            sb.append(",").append(parent);
1591          }
1592        }
1593      }
1594    }
1595    catch (Throwable t)
1596    {
1597      throw new RuntimeException("Unexpected error: "+t, t);
1598    }
1599    return sb.toString();
1600  }
1601
1602  private String getFirstNonEmpty(List<String> values)
1603  {
1604    for (String v : values)
1605    {
1606      v = v.trim();
1607      if (v.length() > 0)
1608      {
1609        return v;
1610      }
1611    }
1612    return null;
1613  }
1614
1615  private void addBrowseClicked(String attrName, JTextComponent textComponent)
1616  {
1617    LocalizableMessage previousTitle = null;
1618    LDAPEntrySelectionPanel.Filter previousFilter = null;
1619    LocalizableMessage title;
1620    LDAPEntrySelectionPanel.Filter filter;
1621    if (browseEntriesDlg == null)
1622    {
1623      browseEntriesPanel = new LDAPEntrySelectionPanel();
1624      browseEntriesPanel.setMultipleSelection(false);
1625      browseEntriesPanel.setInfo(getInfo());
1626      browseEntriesDlg = new GenericDialog(Utilities.getFrame(this),
1627          browseEntriesPanel);
1628      Utilities.centerGoldenMean(browseEntriesDlg,
1629          Utilities.getParentDialog(this));
1630      browseEntriesDlg.setModal(true);
1631    }
1632    else
1633    {
1634      previousTitle = browseEntriesPanel.getTitle();
1635      previousFilter = browseEntriesPanel.getFilter();
1636    }
1637    if (attrName.equalsIgnoreCase(ServerConstants.ATTR_UNIQUE_MEMBER_LC))
1638    {
1639      title = INFO_CTRL_PANEL_ADD_MEMBERS_LABEL.get();
1640      filter = LDAPEntrySelectionPanel.Filter.USERS;
1641    }
1642    else if (attrName.equalsIgnoreCase("ds-target-group-dn"))
1643    {
1644      title = INFO_CTRL_PANEL_CHOOSE_REFERENCE_GROUP.get();
1645      filter = LDAPEntrySelectionPanel.Filter.DYNAMIC_GROUPS;
1646    }
1647    else
1648    {
1649      title = INFO_CTRL_PANEL_CHOOSE_ENTRIES.get();
1650      filter = LDAPEntrySelectionPanel.Filter.DEFAULT;
1651    }
1652    if (!title.equals(previousTitle))
1653    {
1654      browseEntriesPanel.setTitle(title);
1655    }
1656    if (!filter.equals(previousFilter))
1657    {
1658      browseEntriesPanel.setFilter(filter);
1659    }
1660    browseEntriesPanel.setMultipleSelection(!isSingleValue(attrName));
1661
1662    browseEntriesDlg.setVisible(true);
1663    String[] dns = browseEntriesPanel.getDNs();
1664    if (dns.length > 0)
1665    {
1666      if (textComponent instanceof JTextArea)
1667      {
1668        StringBuilder sb = new StringBuilder();
1669        sb.append(textComponent.getText());
1670        for (String dn : dns)
1671        {
1672          if (sb.length() > 0)
1673          {
1674            sb.append("\n");
1675          }
1676          sb.append(dn);
1677        }
1678        textComponent.setText(sb.toString());
1679        textComponent.setCaretPosition(sb.length());
1680      }
1681      else
1682      {
1683        textComponent.setText(dns[0]);
1684      }
1685    }
1686  }
1687
1688  private String getPasswordStringValue(Object o)
1689  {
1690    if (o instanceof byte[])
1691    {
1692      return Base64.encode((byte[])o);
1693    }
1694    else
1695    {
1696      return String.valueOf(o);
1697    }
1698  }
1699
1700  private void updatePanel(ObjectClassValue newValue)
1701  {
1702    CustomSearchResult oldResult = searchResult;
1703    CustomSearchResult newResult = new CustomSearchResult(searchResult.getDN());
1704
1705    for (String attrName : schemaReadOnlyAttributesLowerCase)
1706    {
1707      List<Object> values = searchResult.getAttributeValues(attrName);
1708      if (!values.isEmpty())
1709      {
1710        newResult.set(attrName, values);
1711      }
1712    }
1713    ignoreEntryChangeEvents = true;
1714
1715    Schema schema = getInfo().getServerDescriptor().getSchema();
1716    if (schema != null)
1717    {
1718      ArrayList<String> attributes = new ArrayList<>();
1719      ArrayList<String> ocs = new ArrayList<>();
1720      if (newValue.getStructural() != null)
1721      {
1722        ocs.add(newValue.getStructural().toLowerCase());
1723      }
1724      for (String oc : newValue.getAuxiliary())
1725      {
1726        ocs.add(oc.toLowerCase());
1727      }
1728      for (String oc : ocs)
1729      {
1730        ObjectClass objectClass = schema.getObjectClass(oc);
1731        if (objectClass != null)
1732        {
1733          for (AttributeType attr : objectClass.getRequiredAttributeChain())
1734          {
1735            attributes.add(attr.getNameOrOID().toLowerCase());
1736          }
1737          for (AttributeType attr : objectClass.getOptionalAttributeChain())
1738          {
1739            attributes.add(attr.getNameOrOID().toLowerCase());
1740          }
1741        }
1742      }
1743      for (String attrName : editableOperationalAttrNames)
1744      {
1745        attributes.add(attrName.toLowerCase());
1746      }
1747      for (String attrName : hmEditors.keySet())
1748      {
1749        String attrNoOptions =
1750          Utilities.getAttributeNameWithoutOptions(attrName);
1751        if (!attributes.contains(attrNoOptions))
1752        {
1753          continue;
1754        }
1755        if (isPassword(attrName))
1756        {
1757          List<String> newPwds = getNewPasswords(attrName);
1758          if (newPwds.equals(lastUserPasswords.get(attrName)))
1759          {
1760            List<Object> oldValues = searchResult.getAttributeValues(attrName);
1761            newResult.set(attrName, oldValues);
1762          }
1763          else
1764          {
1765            setValues(newResult, attrName);
1766          }
1767        }
1768        else if (!schemaReadOnlyAttributesLowerCase.contains(
1769          attrName.toLowerCase()))
1770        {
1771          setValues(newResult, attrName);
1772        }
1773      }
1774    }
1775    update(newResult, isReadOnly, treePath);
1776    ignoreEntryChangeEvents = false;
1777    searchResult = oldResult;
1778    notifyListeners();
1779  }
1780
1781  private void updateAttributeVisibility(boolean showAll)
1782  {
1783    for (String attrName : hmLabels.keySet())
1784    {
1785      Component label = hmLabels.get(attrName);
1786      Component comp = hmComponents.get(attrName);
1787
1788      if (showAll || requiredAttrs.contains(attrName))
1789      {
1790        label.setVisible(true);
1791        comp.setVisible(true);
1792      }
1793      else
1794      {
1795        List<EditorComponent> editors = hmEditors.get(attrName);
1796        boolean hasValue = false;
1797
1798        for (EditorComponent editor : editors)
1799        {
1800          hasValue = hasValue(editor);
1801          if (hasValue)
1802          {
1803            break;
1804          }
1805        }
1806        label.setVisible(hasValue);
1807        comp.setVisible(hasValue);
1808      }
1809    }
1810    repaint();
1811  }
1812
1813  private boolean hasValue(String attrName)
1814  {
1815    return !getValues(attrName).isEmpty();
1816  }
1817
1818  private boolean hasValue(EditorComponent editor)
1819  {
1820    Object value = editor.getValue();
1821    if (value instanceof byte[])
1822    {
1823      return ((byte[])value).length > 0;
1824    }
1825    else if (value instanceof String)
1826    {
1827      return ((String)value).trim().length() > 0;
1828    }
1829    else if (value instanceof Collection<?>)
1830    {
1831      return !((Collection<?>)value).isEmpty();
1832    }
1833    return value != null;
1834  }
1835
1836  /** Calls #addBrowseClicked(). */
1837  private final class AddBrowseClickedActionListener implements ActionListener
1838  {
1839    private final JTextComponent tc;
1840    private final String attrName;
1841
1842    private AddBrowseClickedActionListener(JTextComponent tc, String attrName)
1843    {
1844      this.tc = tc;
1845      this.attrName = attrName;
1846    }
1847
1848    @Override
1849    public void actionPerformed(ActionEvent ev)
1850    {
1851      addBrowseClicked(attrName, tc);
1852    }
1853  }
1854
1855  /**
1856   * A class that makes an association between a component (JTextField, a
1857   * BinaryCellValue...) and the associated value that will be used to create
1858   * the modified entry corresponding to the contents of the panel.
1859   *
1860   */
1861  class EditorComponent
1862  {
1863    private Component comp;
1864
1865    /**
1866     * Creates an EditorComponent using a text component.
1867     * @param tf the text component.
1868     */
1869    public EditorComponent(JTextComponent tf)
1870    {
1871      comp = tf;
1872      tf.getDocument().addDocumentListener(new DocumentListener()
1873      {
1874        /** {@inheritDoc} */
1875        public void insertUpdate(DocumentEvent ev)
1876        {
1877          notifyListeners();
1878        }
1879
1880        /** {@inheritDoc} */
1881        public void changedUpdate(DocumentEvent ev)
1882        {
1883          notifyListeners();
1884        }
1885
1886        /** {@inheritDoc} */
1887        public void removeUpdate(DocumentEvent ev)
1888        {
1889          notifyListeners();
1890        }
1891      });
1892    }
1893
1894    /**
1895     * Creates an EditorComponent using a BinaryCellPanel.
1896     * @param binaryPanel the BinaryCellPanel.
1897     */
1898    public EditorComponent(BinaryCellPanel binaryPanel)
1899    {
1900      comp = binaryPanel;
1901    }
1902
1903    /**
1904     * Creates an EditorComponent using a ObjectClassCellPanel.
1905     * @param ocPanel the ObjectClassCellPanel.
1906     */
1907    public EditorComponent(ObjectClassCellPanel ocPanel)
1908    {
1909      comp = ocPanel;
1910    }
1911
1912    /**
1913     * Returns the value that the component is displaying.  The returned value
1914     * is a Set of Strings (for multi-valued attributes), a byte[] for binary
1915     * values or a String for single-valued attributes.   Single-valued
1916     * attributes refer to the definition in the schema (and not to the fact
1917     * that there is a single value for the attribute in this entry).
1918     * @return the value that the component is displaying.
1919     */
1920    public Object getValue()
1921    {
1922      Object returnValue;
1923      if (comp instanceof ObjectClassCellPanel)
1924      {
1925        ObjectClassValue ocDesc = ((ObjectClassCellPanel)comp).getValue();
1926        LinkedHashSet<String> values = new LinkedHashSet<>();
1927        String structural = ocDesc.getStructural();
1928        if (structural != null)
1929        {
1930          values.add(structural);
1931        }
1932        values.addAll(ocDesc.getAuxiliary());
1933        Schema schema = getInfo().getServerDescriptor().getSchema();
1934        if (schema != null && structural != null)
1935        {
1936          ObjectClass oc = schema.getObjectClass(structural.toLowerCase());
1937          if (oc != null)
1938          {
1939            values.addAll(getObjectClassSuperiorValues(oc));
1940          }
1941        }
1942        returnValue = values;
1943      } else if (comp instanceof JTextArea)
1944      {
1945        LinkedHashSet<String> values = new LinkedHashSet<>();
1946        String value = ((JTextArea)comp).getText();
1947        String[] lines = value.split("\n");
1948        for (String line : lines)
1949        {
1950          line = line.trim();
1951          if (line.length() > 0)
1952          {
1953            values.add(line);
1954          }
1955        }
1956        returnValue = values;
1957      }
1958      else if (comp instanceof JTextComponent)
1959      {
1960        returnValue = ((JTextComponent)comp).getText();
1961      }
1962      else
1963      {
1964        Object o = ((BinaryCellPanel)comp).getValue();
1965        if (o instanceof BinaryValue)
1966        {
1967          try
1968          {
1969            returnValue = ((BinaryValue)o).getBytes();
1970          }
1971          catch (ParseException pe)
1972          {
1973            throw new RuntimeException("Unexpected error: "+pe);
1974          }
1975        }
1976        else
1977        {
1978          returnValue = o;
1979        }
1980      }
1981      return returnValue;
1982    }
1983  }
1984}
1985