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