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.schema.AttributeType;
036import org.opends.server.core.DirectoryServer;
037import org.opends.server.types.Attribute;
038import org.opends.server.types.AttributeBuilder;
039import org.forgerock.opendj.ldap.DN;
040import org.opends.server.types.Entry;
041import org.opends.server.types.LDAPException;
042import org.opends.server.types.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.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.hasOptions())
208              {
209                needsMerge = false;
210                tmp.add(new LDAPAttribute(a));
211              }
212            }
213
214            if (needsMerge)
215            {
216              AttributeBuilder builder =
217                  new AttributeBuilder(attrList.getKey());
218              for (Attribute a : attrList.getValue())
219              {
220                builder.addAll(a);
221              }
222              tmp.add(new LDAPAttribute(builder.toAttribute()));
223            }
224          }
225        }
226        else
227        {
228          // LDAPv3
229          for (List<Attribute> attrList : entry.getUserAttributes()
230              .values())
231          {
232            for (Attribute a : attrList)
233            {
234              tmp.add(new LDAPAttribute(a));
235            }
236          }
237
238          for (List<Attribute> attrList : entry
239              .getOperationalAttributes().values())
240          {
241            for (Attribute a : attrList)
242            {
243              tmp.add(new LDAPAttribute(a));
244            }
245          }
246        }
247      }
248
249      attributes = tmp;
250
251      // Since the attributes are mutable, null out the entry for consistency.
252      entry = null;
253    }
254    return attributes;
255  }
256
257
258
259  /**
260   * Retrieves the BER type for this protocol op.
261   *
262   * @return  The BER type for this protocol op.
263   */
264  @Override
265  public byte getType()
266  {
267    return OP_TYPE_SEARCH_RESULT_ENTRY;
268  }
269
270
271
272  /**
273   * Retrieves the name for this protocol op type.
274   *
275   * @return  The name for this protocol op type.
276   */
277  @Override
278  public String getProtocolOpName()
279  {
280    return "Search Result Entry";
281  }
282
283
284
285  /**
286   * Writes this protocol op to an ASN.1 output stream.
287   *
288   * @param stream The ASN.1 output stream to write to.
289   * @throws IOException If a problem occurs while writing to the stream.
290   */
291  @Override
292  public void write(ASN1Writer stream) throws IOException
293  {
294    stream.writeStartSequence(OP_TYPE_SEARCH_RESULT_ENTRY);
295    stream.writeOctetString(dn.toString());
296
297    stream.writeStartSequence();
298    SearchResultEntry tmp = entry;
299    if (ldapVersion == 3 && tmp != null)
300    {
301      for (List<Attribute> attrList : tmp.getUserAttributes()
302          .values())
303      {
304        for (Attribute a : attrList)
305        {
306          writeAttribute(stream, a);
307        }
308      }
309
310      for (List<Attribute> attrList : tmp.getOperationalAttributes()
311          .values())
312      {
313        for (Attribute a : attrList)
314        {
315          writeAttribute(stream, a);
316        }
317      }
318    }
319    else
320    {
321      for (LDAPAttribute attr : getAttributes())
322      {
323        attr.write(stream);
324      }
325    }
326    stream.writeEndSequence();
327
328    stream.writeEndSequence();
329  }
330
331
332
333  /**
334   * Appends a string representation of this LDAP protocol op to the provided
335   * buffer.
336   *
337   * @param  buffer  The buffer to which the string should be appended.
338   */
339  @Override
340  public void toString(StringBuilder buffer)
341  {
342    buffer.append("SearchResultEntry(dn=");
343    buffer.append(dn);
344    buffer.append(", attrs={");
345
346    LinkedList<LDAPAttribute> tmp = getAttributes();
347    if (! tmp.isEmpty())
348    {
349      Iterator<LDAPAttribute> iterator = tmp.iterator();
350      iterator.next().toString(buffer);
351
352      while (iterator.hasNext())
353      {
354        buffer.append(", ");
355        iterator.next().toString(buffer);
356      }
357    }
358
359    buffer.append("})");
360  }
361
362
363
364  /**
365   * Appends a multi-line string representation of this LDAP protocol op to the
366   * provided buffer.
367   *
368   * @param  buffer  The buffer to which the information should be appended.
369   * @param  indent  The number of spaces from the margin that the lines should
370   *                 be indented.
371   */
372  @Override
373  public void toString(StringBuilder buffer, int indent)
374  {
375    StringBuilder indentBuf = new StringBuilder(indent);
376    for (int i=0 ; i < indent; i++)
377    {
378      indentBuf.append(' ');
379    }
380
381    buffer.append(indentBuf);
382    buffer.append("Search Result Entry");
383    buffer.append(EOL);
384
385    buffer.append(indentBuf);
386    buffer.append("  DN:  ");
387    buffer.append(dn);
388    buffer.append(EOL);
389
390    buffer.append("  Attributes:");
391    buffer.append(EOL);
392
393    for (LDAPAttribute attribute : getAttributes())
394    {
395      attribute.toString(buffer, indent+4);
396    }
397  }
398
399
400
401  /**
402   * Appends an LDIF representation of the entry to the provided buffer.
403   *
404   * @param  buffer      The buffer to which the entry should be appended.
405   * @param  wrapColumn  The column at which long lines should be wrapped.
406   */
407  public void toLDIF(StringBuilder buffer, int wrapColumn)
408  {
409    // Add the DN to the buffer.
410    String dnString = dn.toString();
411    int    colsRemaining;
412    if (needsBase64Encoding(dnString))
413    {
414      dnString = Base64.encode(getBytes(dnString));
415      buffer.append("dn:: ");
416
417      colsRemaining = wrapColumn - 5;
418    }
419    else
420    {
421      buffer.append("dn: ");
422
423      colsRemaining = wrapColumn - 4;
424    }
425
426    int dnLength = dnString.length();
427    if (dnLength <= colsRemaining || colsRemaining <= 0)
428    {
429      buffer.append(dnString);
430      buffer.append(EOL);
431    }
432    else
433    {
434      buffer.append(dnString, 0, colsRemaining);
435      buffer.append(EOL);
436
437      int startPos = colsRemaining;
438      while (dnLength - startPos > wrapColumn - 1)
439      {
440        buffer.append(" ");
441        buffer.append(dnString, startPos, startPos+wrapColumn-1);
442        buffer.append(EOL);
443
444        startPos += wrapColumn-1;
445      }
446
447      if (startPos < dnLength)
448      {
449        buffer.append(" ");
450        buffer.append(dnString.substring(startPos));
451        buffer.append(EOL);
452      }
453    }
454
455
456    // Add the attributes to the buffer.
457    for (LDAPAttribute a : getAttributes())
458    {
459      String name       = a.getAttributeType();
460      int    nameLength = name.length();
461
462      for (ByteString v : a.getValues())
463      {
464        String valueString;
465        if (needsBase64Encoding(v))
466        {
467          valueString = Base64.encode(v);
468          buffer.append(name);
469          buffer.append(":: ");
470
471          colsRemaining = wrapColumn - nameLength - 3;
472        }
473        else
474        {
475          valueString = v.toString();
476          buffer.append(name);
477          buffer.append(": ");
478
479          colsRemaining = wrapColumn - nameLength - 2;
480        }
481
482        int valueLength = valueString.length();
483        if (valueLength <= colsRemaining || colsRemaining <= 0)
484        {
485          buffer.append(valueString);
486          buffer.append(EOL);
487        }
488        else
489        {
490          buffer.append(valueString, 0, colsRemaining);
491          buffer.append(EOL);
492
493          int startPos = colsRemaining;
494          while (valueLength - startPos > wrapColumn - 1)
495          {
496            buffer.append(" ");
497            buffer.append(valueString, startPos, startPos+wrapColumn-1);
498            buffer.append(EOL);
499
500            startPos += wrapColumn-1;
501          }
502
503          if (startPos < valueLength)
504          {
505            buffer.append(" ");
506            buffer.append(valueString.substring(startPos));
507            buffer.append(EOL);
508          }
509        }
510      }
511    }
512
513
514    // Make sure to add an extra blank line to ensure that there will be one
515    // between this entry and the next.
516    buffer.append(EOL);
517  }
518
519
520
521  /**
522   * Converts this protocol op to a search result entry.
523   *
524   * @return  The search result entry created from this protocol op.
525   *
526   * @throws  LDAPException  If a problem occurs while trying to create the
527   *                         search result entry.
528   */
529  public SearchResultEntry toSearchResultEntry()
530         throws LDAPException
531  {
532    if (entry != null)
533    {
534      return entry;
535    }
536
537    HashMap<ObjectClass,String> objectClasses = new HashMap<>();
538    HashMap<AttributeType,List<Attribute>> userAttributes = new HashMap<>();
539    HashMap<AttributeType,List<Attribute>> operationalAttributes = new HashMap<>();
540
541
542    for (LDAPAttribute a : getAttributes())
543    {
544      Attribute     attr     = a.toAttribute();
545      AttributeDescription attrDesc = attr.getAttributeDescription();
546      AttributeType attrType = attrDesc.getAttributeType();
547
548      if (attrType.isObjectClass())
549      {
550        for (ByteString os : a.getValues())
551        {
552          String ocName = os.toString();
553          ObjectClass oc =
554               DirectoryServer.getObjectClass(toLowerCase(ocName));
555          if (oc == null)
556          {
557            oc = DirectoryServer.getDefaultObjectClass(ocName);
558          }
559
560          objectClasses.put(oc ,ocName);
561        }
562      }
563      else if (attrType.isOperational())
564      {
565        List<Attribute> attrs = operationalAttributes.get(attrType);
566        if (attrs == null)
567        {
568          attrs = new ArrayList<>(1);
569          operationalAttributes.put(attrType, attrs);
570        }
571        attrs.add(attr);
572      }
573      else
574      {
575        List<Attribute> attrs = userAttributes.get(attrType);
576        if (attrs == null)
577        {
578          attrs = newArrayList(attr);
579          userAttributes.put(attrType, attrs);
580        }
581        else
582        {
583          // Check to see if any of the existing attributes in the list have the
584          // same set of options.  If so, then add the values to that attribute.
585          boolean attributeSeen = false;
586          for (int i = 0; i < attrs.size(); i++) {
587            Attribute ea = attrs.get(i);
588            if (ea.getAttributeDescription().equals(attrDesc))
589            {
590              AttributeBuilder builder = new AttributeBuilder(ea);
591              builder.addAll(attr);
592              attrs.set(i, builder.toAttribute());
593              attributeSeen = true;
594            }
595          }
596          if (!attributeSeen)
597          {
598            // This is the first occurrence of the attribute and options.
599            attrs.add(attr);
600          }
601        }
602      }
603    }
604
605    Entry entry = new Entry(dn, objectClasses, userAttributes,
606                            operationalAttributes);
607    return new SearchResultEntry(entry);
608  }
609
610
611
612  /** Write an attribute without converting to an LDAPAttribute. */
613  private void writeAttribute(ASN1Writer stream, Attribute a)
614      throws IOException
615  {
616    stream.writeStartSequence();
617    stream.writeOctetString(a.getNameWithOptions());
618    stream.writeStartSet();
619    for (ByteString value : a)
620    {
621      stream.writeOctetString(value);
622    }
623    stream.writeEndSequence();
624    stream.writeEndSequence();
625  }
626}
627