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