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