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 2011-2015 ForgeRock AS
026 */
027
028package org.opends.guitools.controlpanel.ui;
029
030import static org.opends.messages.AdminToolMessages.*;
031import static org.opends.server.util.CollectionUtils.*;
032
033import java.awt.Container;
034import java.awt.GridBagConstraints;
035import java.text.ParseException;
036import java.util.ArrayList;
037import java.util.Iterator;
038import java.util.LinkedHashSet;
039import java.util.List;
040import java.util.Set;
041import java.util.SortedSet;
042import java.util.TreeSet;
043
044import javax.swing.JLabel;
045import javax.swing.tree.TreePath;
046
047import org.opends.guitools.controlpanel.datamodel.BinaryValue;
048import org.opends.guitools.controlpanel.datamodel.CustomSearchResult;
049import org.opends.guitools.controlpanel.datamodel.ObjectClassValue;
050import org.opends.guitools.controlpanel.event.ConfigurationChangeEvent;
051import org.opends.guitools.controlpanel.event.LDAPEntryChangedEvent;
052import org.opends.guitools.controlpanel.event.LDAPEntryChangedListener;
053import org.opends.guitools.controlpanel.ui.nodes.BasicNode;
054import org.opends.guitools.controlpanel.util.Utilities;
055import org.forgerock.i18n.LocalizableMessage;
056import org.forgerock.opendj.ldap.schema.Syntax;
057import org.opends.server.schema.SchemaConstants;
058import org.opends.server.types.AttributeType;
059import org.forgerock.opendj.ldap.ByteString;
060import org.opends.server.types.Attributes;
061import org.opends.server.types.Entry;
062import org.opends.server.types.ObjectClass;
063import org.forgerock.opendj.ldap.schema.ObjectClassType;
064import org.opends.server.types.OpenDsException;
065import org.opends.server.types.RDN;
066import org.opends.server.types.Schema;
067import org.opends.server.util.Base64;
068import org.opends.server.util.ServerConstants;
069
070/**
071 * Abstract class containing code shared by the different LDAP entry view
072 * panels (Simplified View, Attribute View and LDIF View).
073 *
074 */
075public abstract class ViewEntryPanel extends StatusGenericPanel
076{
077  private static final long serialVersionUID = -1908757626234678L;
078  /**
079   * The read-only attributes as they appear on the schema.
080   */
081  protected SortedSet<String> schemaReadOnlyAttributes = new TreeSet<>();
082  /**
083   * The read-only attributes in lower case.
084   */
085  protected SortedSet<String> schemaReadOnlyAttributesLowerCase = new TreeSet<>();
086  /**
087   * The editable operational attributes.
088   */
089  protected SortedSet<String> editableOperationalAttrNames = new TreeSet<>();
090  private JLabel title= Utilities.createDefaultLabel();
091
092  private Set<LDAPEntryChangedListener> listeners = new LinkedHashSet<>();
093
094  /**
095   * Whether the entry change events should be ignored or not.
096   */
097  protected boolean ignoreEntryChangeEvents;
098
099  /**
100   * Static boolean used to know whether only attributes with values should be
101   * displayed or not.
102   */
103  protected static boolean displayOnlyWithAttrs = true;
104
105  /** {@inheritDoc} */
106  public void okClicked()
107  {
108    // No ok button
109  }
110
111  /**
112   * Returns an Entry object representing what the panel is displaying.
113   * @return an Entry object representing what the panel is displaying.
114   * @throws OpenDsException if the entry cannot be generated (in particular if
115   * the user provided invalid data).
116   */
117  public abstract Entry getEntry() throws OpenDsException;
118
119  /**
120   * Updates the contents of the panel.
121   * @param sr the search result to be used to update the panel.
122   * @param isReadOnly whether the entry is read-only or not.
123   * @param path the tree path associated with the entry in the tree.
124   */
125  public abstract void update(CustomSearchResult sr, boolean isReadOnly,
126      TreePath path);
127
128  /**
129   * Adds a title panel to the container.
130   * @param c the container where the title panel must be added.
131   * @param gbc the grid bag constraints to be used.
132   */
133  protected void addTitlePanel(Container c, GridBagConstraints gbc)
134  {
135    c.add(title, gbc);
136  }
137
138  /**
139   * Whether the schema must be checked or not.
140   * @return <CODE>true</CODE> if the server is configured to check schema and
141   * <CODE>false</CODE> otherwise.
142   */
143  protected boolean checkSchema()
144  {
145    return getInfo().getServerDescriptor().isSchemaEnabled();
146  }
147
148  /**
149   * Adds an LDAP entry change listener.
150   * @param listener the listener.
151   */
152  public void addLDAPEntryChangedListener(LDAPEntryChangedListener listener)
153  {
154    listeners.add(listener);
155  }
156
157  /**
158   * Removes an LDAP entry change listener.
159   * @param listener the listener.
160   */
161  public void removeLDAPEntryChangedListener(LDAPEntryChangedListener listener)
162  {
163    listeners.remove(listener);
164  }
165
166  /** {@inheritDoc} */
167  public boolean requiresBorder()
168  {
169    return true;
170  }
171
172  /**
173   * Returns the DN of the entry that the user is editing (it might differ
174   * from the DN of the entry in the tree if the user modified the DN).
175   * @return the DN of the entry that the user is editing.
176   */
177  protected abstract String getDisplayedDN();
178
179  /** Notifies the entry changed listeners that the entry changed. */
180  protected void notifyListeners()
181  {
182    if (ignoreEntryChangeEvents)
183    {
184      return;
185    }
186    // TODO: With big entries this is pretty slow.  Until there is a fix, try
187    // simply to update the dn
188    Entry entry = null;
189    String dn = getDisplayedDN();
190    if (dn != null && !dn.equals(title.getText()))
191    {
192      title.setText(dn);
193    }
194    LDAPEntryChangedEvent ev = new LDAPEntryChangedEvent(this, entry);
195    for (LDAPEntryChangedListener listener : listeners)
196    {
197      listener.entryChanged(ev);
198    }
199  }
200
201  /**
202   * Updates the title panel with the provided entry.
203   * @param sr the search result.
204   * @param path the path to the node of the entry selected in the tree.  Used
205   * to display the same icon as in the tree.
206   */
207  protected void updateTitle(CustomSearchResult sr, TreePath path)
208  {
209    String dn = sr.getDN();
210    if (dn != null && dn.length() > 0)
211    {
212      title.setText(sr.getDN());
213    }
214    else if (path != null)
215    {
216      BasicNode node = (BasicNode)path.getLastPathComponent();
217      title.setText(node.getDisplayName());
218    }
219
220    if (path != null)
221    {
222      BasicNode node = (BasicNode)path.getLastPathComponent();
223      title.setIcon(node.getIcon());
224    }
225    else
226    {
227      title.setIcon(null);
228    }
229
230    List<Object> ocs =
231      sr.getAttributeValues(ServerConstants.OBJECTCLASS_ATTRIBUTE_TYPE_NAME);
232    Schema schema = getInfo().getServerDescriptor().getSchema();
233    if (!ocs.isEmpty() && schema != null)
234    {
235      ObjectClassValue ocDesc = getObjectClassDescriptor(ocs, schema);
236      StringBuilder sb = new StringBuilder();
237      sb.append("<html>");
238      if (ocDesc.getStructural() != null)
239      {
240        sb.append(INFO_CTRL_OBJECTCLASS_DESCRIPTOR.get(ocDesc.getStructural()));
241      }
242      if (!ocDesc.getAuxiliary().isEmpty())
243      {
244        if (sb.length() > 0)
245        {
246          sb.append("<br>");
247        }
248        sb.append(INFO_CTRL_AUXILIARY_OBJECTCLASS_DESCRIPTOR.get(
249            Utilities.getStringFromCollection(ocDesc.getAuxiliary(), ", ")));
250      }
251      title.setToolTipText(sb.toString());
252    }
253    else
254    {
255      title.setToolTipText(null);
256    }
257  }
258
259  /**
260   * Returns an object class value representing all the object class values of
261   * the entry.
262   * @param ocValues the list of object class values.
263   * @param schema the schema.
264   * @return an object class value representing all the object class values of
265   * the entry.
266   */
267  protected ObjectClassValue getObjectClassDescriptor(List<Object> ocValues,
268      Schema schema)
269  {
270    ObjectClass structuralObjectClass = null;
271    SortedSet<String> auxiliaryClasses = new TreeSet<>();
272    for (Object o : ocValues)
273    {
274      ObjectClass objectClass =
275        schema.getObjectClass(((String)o).toLowerCase());
276      if (objectClass != null)
277      {
278        if (objectClass.getObjectClassType() == ObjectClassType.STRUCTURAL)
279        {
280          if (structuralObjectClass == null)
281          {
282            structuralObjectClass = objectClass;
283          }
284          else
285          {
286            if (objectClass.isDescendantOf(structuralObjectClass))
287            {
288              structuralObjectClass = objectClass;
289            }
290          }
291        }
292        else
293        {
294          String name = objectClass.getNameOrOID();
295          if (!name.equals(SchemaConstants.TOP_OBJECTCLASS_NAME))
296          {
297            auxiliaryClasses.add(objectClass.getNameOrOID());
298          }
299        }
300      }
301    }
302    String structural = structuralObjectClass != null ?
303        structuralObjectClass.getNameOrOID() : null;
304    return new ObjectClassValue(structural, auxiliaryClasses);
305  }
306
307  /**
308   * Adds the values in the RDN to the entry definition.
309   * @param entry the entry to be updated.
310   */
311  protected void addValuesInRDN(Entry entry)
312  {
313//  Add the values in the RDN if  they are not there
314    RDN rdn = entry.getName().rdn();
315    for (int i=0; i<rdn.getNumValues(); i++)
316    {
317      String attrName = rdn.getAttributeName(i);
318      ByteString value = rdn.getAttributeValue(i);
319      List<org.opends.server.types.Attribute> attrs =
320        entry.getAttribute(attrName.toLowerCase());
321      boolean done = false;
322      if (attrs != null)
323      {
324        for (org.opends.server.types.Attribute attr : attrs)
325        {
326          if (attr.getNameWithOptions().equals(attrName))
327          {
328            ArrayList<ByteString> newValues = new ArrayList<>();
329            Iterator<ByteString> it = attr.iterator();
330            while (it.hasNext())
331            {
332              newValues.add(it.next());
333            }
334            newValues.add(value);
335            entry.addAttribute(attr, newValues);
336            done = true;
337            break;
338          }
339        }
340      }
341      if (!done)
342      {
343        org.opends.server.types.Attribute attr =
344          Attributes.create(rdn.getAttributeType(i), value);
345        entry.addAttribute(attr, newArrayList(value));
346      }
347    }
348  }
349
350  /** {@inheritDoc} */
351  public LocalizableMessage getTitle()
352  {
353    return INFO_CTRL_PANEL_EDIT_LDAP_ENTRY_TITLE.get();
354  }
355
356  /** {@inheritDoc} */
357  public void configurationChanged(ConfigurationChangeEvent ev)
358  {
359    Schema schema = ev.getNewDescriptor().getSchema();
360    if (schema != null && schemaReadOnlyAttributes.isEmpty())
361    {
362      schemaReadOnlyAttributes.clear();
363      schemaReadOnlyAttributesLowerCase.clear();
364      for (AttributeType attr : schema.getAttributeTypes().values())
365      {
366        if (attr.isNoUserModification())
367        {
368          String attrName = attr.getNameOrOID();
369          schemaReadOnlyAttributes.add(attrName);
370          schemaReadOnlyAttributesLowerCase.add(attrName.toLowerCase());
371        }
372        else if (attr.isOperational())
373        {
374          editableOperationalAttrNames.add(attr.getNameOrOID());
375        }
376      }
377    }
378  }
379
380  /**
381   * Appends the LDIF lines corresponding to the different values of an
382   * attribute to the provided StringBuilder.
383   * @param sb the StringBuilder that must be updated.
384   * @param attrName the attribute name.
385   * @param values the attribute values.
386   */
387  protected void appendLDIFLines(StringBuilder sb, String attrName,
388      List<Object> values)
389  {
390    for (Object value : values)
391    {
392      appendLDIFLine(sb, attrName, value);
393    }
394  }
395
396  /**
397   * Appends the LDIF line corresponding to the value of an
398   * attribute to the provided StringBuilder.
399   * @param sb the StringBuilder that must be updated.
400   * @param attrName the attribute name.
401   * @param value the attribute value.
402   */
403  protected void appendLDIFLine(StringBuilder sb, String attrName, Object value)
404  {
405    if (value instanceof ObjectClassValue)
406    {
407      ObjectClassValue ocValue = (ObjectClassValue)value;
408      if (ocValue.getStructural() != null)
409      {
410        sb.append("\n");
411        sb.append(attrName).append(": ").append(ocValue.getStructural());
412        Schema schema = getInfo().getServerDescriptor().getSchema();
413        if (schema != null)
414        {
415          ObjectClass oc =
416            schema.getObjectClass(ocValue.getStructural().toLowerCase());
417          if (oc != null)
418          {
419            Set<String> names = getObjectClassSuperiorValues(oc);
420            for (String name : names)
421            {
422              sb.append("\n");
423              sb.append(attrName).append(": ").append(name);
424            }
425          }
426        }
427      }
428      for (String v : ocValue.getAuxiliary())
429      {
430        sb.append("\n");
431        sb.append(attrName).append(": ").append(v);
432      }
433    }
434    else if (value instanceof byte[])
435    {
436      if (((byte[])value).length > 0)
437      {
438        sb.append("\n");
439        sb.append(attrName).append(":: ").append(Base64.encode((byte[])value));
440      }
441    }
442    else if (value instanceof BinaryValue)
443    {
444      sb.append("\n");
445      sb.append(attrName).append(":: ").append(((BinaryValue)value).getBase64());
446    }
447    else if (String.valueOf(value).trim().length() > 0)
448    {
449      sb.append("\n");
450      sb.append(attrName).append(": ").append(value);
451    }
452  }
453
454  /**
455   * Returns <CODE>true</CODE> if the provided attribute name has binary syntax
456   * and <CODE>false</CODE> otherwise.
457   * @param attrName the attribute name.
458   * @return <CODE>true</CODE> if the provided attribute name has binary syntax
459   * and <CODE>false</CODE> otherwise.
460   */
461  protected boolean isBinary(String attrName)
462  {
463    Schema schema = getInfo().getServerDescriptor().getSchema();
464    return Utilities.hasBinarySyntax(attrName, schema);
465  }
466
467  /**
468   * Returns <CODE>true</CODE> if the provided attribute name has password
469   * syntax and <CODE>false</CODE> otherwise.
470   * @param attrName the attribute name.
471   * @return <CODE>true</CODE> if the provided attribute name has password
472   * syntax and <CODE>false</CODE> otherwise.
473   */
474  protected boolean isPassword(String attrName)
475  {
476    Schema schema = getInfo().getServerDescriptor().getSchema();
477    return Utilities.hasPasswordSyntax(attrName, schema);
478  }
479
480  /**
481   * Returns <CODE>true</CODE> if the provided attribute name has certificate
482   * syntax and <CODE>false</CODE> otherwise.
483   * @param attrName the attribute name.
484   * @param schema the schema.
485   * @return <CODE>true</CODE> if the provided attribute name has certificate
486   * syntax and <CODE>false</CODE> otherwise.
487   */
488  protected boolean hasCertificateSyntax(String attrName, Schema schema)
489  {
490    boolean isCertificate = false;
491    // Check all the attributes that we consider binaries.
492    if (schema != null)
493    {
494      AttributeType attr = schema.getAttributeType(
495          Utilities.getAttributeNameWithoutOptions(attrName).toLowerCase());
496      if (attr != null)
497      {
498        Syntax syntax = attr.getSyntax();
499        if (syntax != null)
500        {
501          isCertificate = syntax.getOID().equals(
502              SchemaConstants.SYNTAX_CERTIFICATE_OID);
503        }
504      }
505    }
506    return isCertificate;
507  }
508
509  /**
510   * Gets the values associated with a given attribute.  The values are the
511   * ones displayed in the panel.
512   * @param attrName the attribute name.
513   * @return the values associated with a given attribute.
514   */
515  protected abstract List<Object> getValues(String attrName);
516
517  /**
518   * Sets the values displayed in the panel for a given attribute in the
519   * provided search result.
520   * @param sr the search result to be updated.
521   * @param attrName the attribute name.
522   */
523  protected void setValues(CustomSearchResult sr, String attrName)
524  {
525    List<Object> values = getValues(attrName);
526    List<Object> valuesToSet = new ArrayList<>();
527    for (Object value : values)
528    {
529      if (value instanceof ObjectClassValue)
530      {
531        ObjectClassValue ocValue = (ObjectClassValue)value;
532        if (ocValue.getStructural() != null)
533        {
534          valuesToSet.add(ocValue.getStructural());
535        }
536        valuesToSet.addAll(ocValue.getAuxiliary());
537      }
538      else if (value instanceof byte[])
539      {
540        valuesToSet.add(value);
541      }
542      else if (value instanceof BinaryValue)
543      {
544        try
545        {
546          valuesToSet.add(((BinaryValue)value).getBytes());
547        }
548        catch (ParseException pe)
549        {
550         throw new RuntimeException("Unexpected error: "+pe, pe);
551        }
552      }
553      else
554      {
555        if (String.valueOf(value).trim().length() > 0)
556        {
557          valuesToSet.add(String.valueOf(value));
558        }
559      }
560    }
561    if (!valuesToSet.isEmpty())
562    {
563      sr.set(attrName, valuesToSet);
564    }
565  }
566
567  /**
568   * Returns <CODE>true</CODE> if the provided attribute name is an editable
569   * attribute and <CODE>false</CODE> otherwise.
570   * @param attrName the attribute name.
571   * @param schema the schema.
572   * @return <CODE>true</CODE> if the provided attribute name is an editable
573   * attribute and <CODE>false</CODE> otherwise.
574   */
575  public static boolean isEditable(String attrName, Schema schema)
576  {
577    boolean isEditable = false;
578    attrName = Utilities.getAttributeNameWithoutOptions(attrName);
579    if (schema != null)
580    {
581      AttributeType attrType = schema.getAttributeType(attrName.toLowerCase());
582      if (attrType != null)
583      {
584        isEditable = !attrType.isNoUserModification();
585      }
586    }
587    return isEditable;
588  }
589
590  /**
591   * Returns the list of superior object classes (to top) for a given object
592   * class.
593   * @param oc the object class.
594   * @return the set of superior object classes for a given object classes.
595   */
596  protected Set<String> getObjectClassSuperiorValues(
597      ObjectClass oc)
598  {
599    Set<String> names = new LinkedHashSet<>();
600    Set<ObjectClass> parents = oc.getSuperiorClasses();
601    if (parents != null && !parents.isEmpty())
602    {
603      for (ObjectClass parent : parents)
604      {
605        names.add(parent.getNameOrOID());
606        names.addAll(getObjectClassSuperiorValues(parent));
607      }
608    }
609    return names;
610  }
611}