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