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 2006-2009 Sun Microsystems, Inc.
015 * Portions Copyright 2014-2016 ForgeRock AS.
016 */
017package org.opends.server.protocols.ldap;
018
019import static org.opends.server.protocols.ldap.LDAPConstants.*;
020import static org.opends.server.util.CollectionUtils.*;
021import static org.opends.server.util.ServerConstants.*;
022import static org.opends.server.util.StaticUtils.*;
023
024import java.io.IOException;
025import java.util.ArrayList;
026import java.util.HashMap;
027import java.util.Iterator;
028import java.util.LinkedList;
029import java.util.List;
030import java.util.Map;
031
032import org.forgerock.opendj.io.ASN1Writer;
033import org.forgerock.opendj.ldap.AttributeDescription;
034import org.forgerock.opendj.ldap.ByteString;
035import org.forgerock.opendj.ldap.DN;
036import org.forgerock.opendj.ldap.schema.AttributeType;
037import org.opends.server.core.DirectoryServer;
038import org.opends.server.types.Attribute;
039import org.opends.server.types.AttributeBuilder;
040import org.opends.server.types.Entry;
041import org.opends.server.types.LDAPException;
042import org.forgerock.opendj.ldap.schema.ObjectClass;
043import org.opends.server.types.SearchResultEntry;
044import org.opends.server.util.Base64;
045
046/**
047 * This class defines the structures and methods for an LDAP search result entry
048 * protocol op, which is used to return entries that match the associated search
049 * criteria.
050 */
051public class SearchResultEntryProtocolOp
052       extends ProtocolOp
053{
054  /** The set of attributes for this search entry. */
055  private LinkedList<LDAPAttribute> attributes;
056
057  /** The DN for this search entry. */
058  private final DN dn;
059
060  /** The underlying search result entry. */
061  private SearchResultEntry entry;
062
063  /** The LDAP version (determines how attribute options are handled). */
064  private final int ldapVersion;
065
066
067
068  /**
069   * Creates a new LDAP search result entry protocol op with the specified DN
070   * and no attributes.
071   *
072   * @param  dn  The DN for this search result entry.
073   */
074  public SearchResultEntryProtocolOp(DN dn)
075  {
076    this(dn, null, null, 3);
077  }
078
079
080
081  /**
082   * Creates a new LDAP search result entry protocol op with the specified DN
083   * and set of attributes.
084   *
085   * @param  dn          The DN for this search result entry.
086   * @param  attributes  The set of attributes for this search result entry.
087   */
088  public SearchResultEntryProtocolOp(DN dn,
089                                     LinkedList<LDAPAttribute> attributes)
090  {
091    this(dn, attributes, null, 3);
092  }
093
094
095
096  /**
097   * Creates a new search result entry protocol op from the provided search
098   * result entry.
099   *
100   * @param  searchEntry  The search result entry object to use to create this
101   *                      search result entry protocol op.
102   */
103  public SearchResultEntryProtocolOp(SearchResultEntry searchEntry)
104  {
105    this(searchEntry.getName(), null, searchEntry, 3);
106  }
107
108
109
110  /**
111   * Creates a new search result entry protocol op from the provided search
112   * result entry and ldap protocol version.
113   *
114   * @param  searchEntry  The search result entry object to use to create this
115   *                      search result entry protocol op.
116   * @param ldapVersion The version of the LDAP protocol.
117   */
118  public SearchResultEntryProtocolOp(SearchResultEntry searchEntry,
119          int ldapVersion)
120  {
121    this(searchEntry.getName(), null, searchEntry, ldapVersion);
122  }
123
124
125
126  /** Generic constructor. */
127  private SearchResultEntryProtocolOp(DN dn,
128      LinkedList<LDAPAttribute> attributes, SearchResultEntry searchEntry,
129      int ldapVersion)
130  {
131    this.dn = dn;
132    this.attributes = attributes;
133    this.entry = searchEntry;
134    this.ldapVersion = ldapVersion;
135  }
136
137
138
139  /**
140   * Retrieves the DN for this search result entry.
141   *
142   * @return  The DN for this search result entry.
143   */
144  public DN getDN()
145  {
146    return dn;
147  }
148
149
150  /**
151   * Retrieves the set of attributes for this search result entry.  The returned
152   * list may be altered by the caller.
153   *
154   * @return  The set of attributes for this search result entry.
155   */
156  public LinkedList<LDAPAttribute> getAttributes()
157  {
158    LinkedList<LDAPAttribute> tmp = attributes;
159    if (tmp == null)
160    {
161      tmp = new LinkedList<>();
162      if (entry != null)
163      {
164        if (ldapVersion == 2)
165        {
166          // Merge attributes having the same type into a single
167          // attribute.
168          boolean needsMerge;
169          Map<AttributeType, List<Attribute>> attrs =
170              entry.getUserAttributes();
171          for (Map.Entry<AttributeType, List<Attribute>> attrList : attrs
172              .entrySet())
173          {
174            needsMerge = true;
175
176            if (attrList != null && attrList.getValue().size() == 1)
177            {
178              Attribute a = attrList.getValue().get(0);
179              if (!a.getAttributeDescription().hasOptions())
180              {
181                needsMerge = false;
182                tmp.add(new LDAPAttribute(a));
183              }
184            }
185
186            if (needsMerge)
187            {
188              AttributeBuilder builder =
189                  new AttributeBuilder(attrList.getKey());
190              for (Attribute a : attrList.getValue())
191              {
192                builder.addAll(a);
193              }
194              tmp.add(new LDAPAttribute(builder.toAttribute()));
195            }
196          }
197
198          attrs = entry.getOperationalAttributes();
199          for (Map.Entry<AttributeType, List<Attribute>> attrList : attrs
200              .entrySet())
201          {
202            needsMerge = true;
203
204            if (attrList != null && attrList.getValue().size() == 1)
205            {
206              Attribute a = attrList.getValue().get(0);
207              if (!a.getAttributeDescription().hasOptions())
208              {
209                needsMerge = false;
210                tmp.add(new LDAPAttribute(a));
211              }
212            }
213
214            if (needsMerge)
215            {
216              AttributeBuilder builder = new AttributeBuilder(attrList.getKey());
217              for (Attribute a : attrList.getValue())
218              {
219                builder.addAll(a);
220              }
221              tmp.add(new LDAPAttribute(builder.toAttribute()));
222            }
223          }
224        }
225        else
226        {
227          // LDAPv3
228          for (List<Attribute> attrList : entry.getUserAttributes().values())
229          {
230            for (Attribute a : attrList)
231            {
232              tmp.add(new LDAPAttribute(a));
233            }
234          }
235
236          for (List<Attribute> attrList : entry.getOperationalAttributes().values())
237          {
238            for (Attribute a : attrList)
239            {
240              tmp.add(new LDAPAttribute(a));
241            }
242          }
243        }
244      }
245
246      attributes = tmp;
247
248      // Since the attributes are mutable, null out the entry for consistency.
249      entry = null;
250    }
251    return attributes;
252  }
253
254
255
256  /**
257   * Retrieves the BER type for this protocol op.
258   *
259   * @return  The BER type for this protocol op.
260   */
261  @Override
262  public byte getType()
263  {
264    return OP_TYPE_SEARCH_RESULT_ENTRY;
265  }
266
267
268
269  /**
270   * Retrieves the name for this protocol op type.
271   *
272   * @return  The name for this protocol op type.
273   */
274  @Override
275  public String getProtocolOpName()
276  {
277    return "Search Result Entry";
278  }
279
280
281
282  /**
283   * Writes this protocol op to an ASN.1 output stream.
284   *
285   * @param stream The ASN.1 output stream to write to.
286   * @throws IOException If a problem occurs while writing to the stream.
287   */
288  @Override
289  public void write(ASN1Writer stream) throws IOException
290  {
291    stream.writeStartSequence(OP_TYPE_SEARCH_RESULT_ENTRY);
292    stream.writeOctetString(dn.toString());
293
294    stream.writeStartSequence();
295    SearchResultEntry tmp = entry;
296    if (ldapVersion == 3 && tmp != null)
297    {
298      for (List<Attribute> attrList : tmp.getUserAttributes()
299          .values())
300      {
301        for (Attribute a : attrList)
302        {
303          writeAttribute(stream, a);
304        }
305      }
306
307      for (List<Attribute> attrList : tmp.getOperationalAttributes()
308          .values())
309      {
310        for (Attribute a : attrList)
311        {
312          writeAttribute(stream, a);
313        }
314      }
315    }
316    else
317    {
318      for (LDAPAttribute attr : getAttributes())
319      {
320        attr.write(stream);
321      }
322    }
323    stream.writeEndSequence();
324
325    stream.writeEndSequence();
326  }
327
328
329
330  /**
331   * Appends a string representation of this LDAP protocol op to the provided
332   * buffer.
333   *
334   * @param  buffer  The buffer to which the string should be appended.
335   */
336  @Override
337  public void toString(StringBuilder buffer)
338  {
339    buffer.append("SearchResultEntry(dn=");
340    buffer.append(dn);
341    buffer.append(", attrs={");
342
343    LinkedList<LDAPAttribute> tmp = getAttributes();
344    if (! tmp.isEmpty())
345    {
346      Iterator<LDAPAttribute> iterator = tmp.iterator();
347      iterator.next().toString(buffer);
348
349      while (iterator.hasNext())
350      {
351        buffer.append(", ");
352        iterator.next().toString(buffer);
353      }
354    }
355
356    buffer.append("})");
357  }
358
359
360
361  /**
362   * Appends a multi-line string representation of this LDAP protocol op to the
363   * provided buffer.
364   *
365   * @param  buffer  The buffer to which the information should be appended.
366   * @param  indent  The number of spaces from the margin that the lines should
367   *                 be indented.
368   */
369  @Override
370  public void toString(StringBuilder buffer, int indent)
371  {
372    StringBuilder indentBuf = new StringBuilder(indent);
373    for (int i=0 ; i < indent; i++)
374    {
375      indentBuf.append(' ');
376    }
377
378    buffer.append(indentBuf);
379    buffer.append("Search Result Entry");
380    buffer.append(EOL);
381
382    buffer.append(indentBuf);
383    buffer.append("  DN:  ");
384    buffer.append(dn);
385    buffer.append(EOL);
386
387    buffer.append("  Attributes:");
388    buffer.append(EOL);
389
390    for (LDAPAttribute attribute : getAttributes())
391    {
392      attribute.toString(buffer, indent+4);
393    }
394  }
395
396
397
398  /**
399   * Appends an LDIF representation of the entry to the provided buffer.
400   *
401   * @param  buffer      The buffer to which the entry should be appended.
402   * @param  wrapColumn  The column at which long lines should be wrapped.
403   */
404  public void toLDIF(StringBuilder buffer, int wrapColumn)
405  {
406    // Add the DN to the buffer.
407    String dnString = dn.toString();
408    int    colsRemaining;
409    if (needsBase64Encoding(dnString))
410    {
411      dnString = Base64.encode(getBytes(dnString));
412      buffer.append("dn:: ");
413
414      colsRemaining = wrapColumn - 5;
415    }
416    else
417    {
418      buffer.append("dn: ");
419
420      colsRemaining = wrapColumn - 4;
421    }
422
423    int dnLength = dnString.length();
424    if (dnLength <= colsRemaining || colsRemaining <= 0)
425    {
426      buffer.append(dnString);
427      buffer.append(EOL);
428    }
429    else
430    {
431      buffer.append(dnString, 0, colsRemaining);
432      buffer.append(EOL);
433
434      int startPos = colsRemaining;
435      while (dnLength - startPos > wrapColumn - 1)
436      {
437        buffer.append(" ");
438        buffer.append(dnString, startPos, startPos+wrapColumn-1);
439        buffer.append(EOL);
440
441        startPos += wrapColumn-1;
442      }
443
444      if (startPos < dnLength)
445      {
446        buffer.append(" ");
447        buffer.append(dnString.substring(startPos));
448        buffer.append(EOL);
449      }
450    }
451
452
453    // Add the attributes to the buffer.
454    for (LDAPAttribute a : getAttributes())
455    {
456      String name       = a.getAttributeType();
457      int    nameLength = name.length();
458
459      for (ByteString v : a.getValues())
460      {
461        String valueString;
462        if (needsBase64Encoding(v))
463        {
464          valueString = Base64.encode(v);
465          buffer.append(name);
466          buffer.append(":: ");
467
468          colsRemaining = wrapColumn - nameLength - 3;
469        }
470        else
471        {
472          valueString = v.toString();
473          buffer.append(name);
474          buffer.append(": ");
475
476          colsRemaining = wrapColumn - nameLength - 2;
477        }
478
479        int valueLength = valueString.length();
480        if (valueLength <= colsRemaining || colsRemaining <= 0)
481        {
482          buffer.append(valueString);
483          buffer.append(EOL);
484        }
485        else
486        {
487          buffer.append(valueString, 0, colsRemaining);
488          buffer.append(EOL);
489
490          int startPos = colsRemaining;
491          while (valueLength - startPos > wrapColumn - 1)
492          {
493            buffer.append(" ");
494            buffer.append(valueString, startPos, startPos+wrapColumn-1);
495            buffer.append(EOL);
496
497            startPos += wrapColumn-1;
498          }
499
500          if (startPos < valueLength)
501          {
502            buffer.append(" ");
503            buffer.append(valueString.substring(startPos));
504            buffer.append(EOL);
505          }
506        }
507      }
508    }
509
510
511    // Make sure to add an extra blank line to ensure that there will be one
512    // between this entry and the next.
513    buffer.append(EOL);
514  }
515
516
517
518  /**
519   * Converts this protocol op to a search result entry.
520   *
521   * @return  The search result entry created from this protocol op.
522   *
523   * @throws  LDAPException  If a problem occurs while trying to create the
524   *                         search result entry.
525   */
526  public SearchResultEntry toSearchResultEntry()
527         throws LDAPException
528  {
529    if (entry != null)
530    {
531      return entry;
532    }
533
534    HashMap<ObjectClass,String> objectClasses = new HashMap<>();
535    HashMap<AttributeType,List<Attribute>> userAttributes = new HashMap<>();
536    HashMap<AttributeType,List<Attribute>> operationalAttributes = new HashMap<>();
537
538
539    for (LDAPAttribute a : getAttributes())
540    {
541      Attribute     attr     = a.toAttribute();
542      AttributeDescription attrDesc = attr.getAttributeDescription();
543      AttributeType attrType = attrDesc.getAttributeType();
544
545      if (attrType.isObjectClass())
546      {
547        for (ByteString os : a.getValues())
548        {
549          String ocName = os.toString();
550          ObjectClass oc = DirectoryServer.getSchema().getObjectClass(ocName);
551          objectClasses.put(oc, ocName);
552        }
553      }
554      else if (attrType.isOperational())
555      {
556        List<Attribute> attrs = operationalAttributes.get(attrType);
557        if (attrs == null)
558        {
559          attrs = new ArrayList<>(1);
560          operationalAttributes.put(attrType, attrs);
561        }
562        attrs.add(attr);
563      }
564      else
565      {
566        List<Attribute> attrs = userAttributes.get(attrType);
567        if (attrs == null)
568        {
569          attrs = newArrayList(attr);
570          userAttributes.put(attrType, attrs);
571        }
572        else
573        {
574          // Check to see if any of the existing attributes in the list have the
575          // same set of options.  If so, then add the values to that attribute.
576          boolean attributeSeen = false;
577          for (int i = 0; i < attrs.size(); i++) {
578            Attribute ea = attrs.get(i);
579            if (ea.getAttributeDescription().equals(attrDesc))
580            {
581              AttributeBuilder builder = new AttributeBuilder(ea);
582              builder.addAll(attr);
583              attrs.set(i, builder.toAttribute());
584              attributeSeen = true;
585            }
586          }
587          if (!attributeSeen)
588          {
589            // This is the first occurrence of the attribute and options.
590            attrs.add(attr);
591          }
592        }
593      }
594    }
595
596    Entry entry = new Entry(dn, objectClasses, userAttributes,
597                            operationalAttributes);
598    return new SearchResultEntry(entry);
599  }
600
601
602
603  /** Write an attribute without converting to an LDAPAttribute. */
604  private void writeAttribute(ASN1Writer stream, Attribute a)
605      throws IOException
606  {
607    stream.writeStartSequence();
608    stream.writeOctetString(a.getAttributeDescription().toString());
609    stream.writeStartSet();
610    for (ByteString value : a)
611    {
612      stream.writeOctetString(value);
613    }
614    stream.writeEndSequence();
615    stream.writeEndSequence();
616  }
617}
618