001    /*
002     * Copyright 2009-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2015 UnboundID Corp.
007     *
008     * This program is free software; you can redistribute it and/or modify
009     * it under the terms of the GNU General Public License (GPLv2 only)
010     * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011     * as published by the Free Software Foundation.
012     *
013     * This program is distributed in the hope that it will be useful,
014     * but WITHOUT ANY WARRANTY; without even the implied warranty of
015     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016     * GNU General Public License for more details.
017     *
018     * You should have received a copy of the GNU General Public License
019     * along with this program; if not, see <http://www.gnu.org/licenses>.
020     */
021    package com.unboundid.ldap.sdk.unboundidds.controls;
022    
023    
024    
025    import java.util.ArrayList;
026    import java.util.Collection;
027    import java.util.Collections;
028    import java.util.Iterator;
029    import java.util.List;
030    
031    import com.unboundid.asn1.ASN1Element;
032    import com.unboundid.asn1.ASN1OctetString;
033    import com.unboundid.asn1.ASN1Sequence;
034    import com.unboundid.ldap.sdk.Attribute;
035    import com.unboundid.ldap.sdk.Entry;
036    import com.unboundid.ldap.sdk.LDAPException;
037    import com.unboundid.ldap.sdk.ReadOnlyEntry;
038    import com.unboundid.ldap.sdk.ResultCode;
039    import com.unboundid.util.NotMutable;
040    import com.unboundid.util.ThreadSafety;
041    import com.unboundid.util.ThreadSafetyLevel;
042    
043    import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*;
044    import static com.unboundid.util.Debug.*;
045    import static com.unboundid.util.StaticUtils.*;
046    
047    
048    
049    /**
050     * <BLOCKQUOTE>
051     *   <B>NOTE:</B>  This class is part of the Commercial Edition of the UnboundID
052     *   LDAP SDK for Java.  It is not available for use in applications that
053     *   include only the Standard Edition of the LDAP SDK, and is not supported for
054     *   use in conjunction with non-UnboundID products.
055     * </BLOCKQUOTE>
056     * This class provides a joined entry, which is a read-only representation of an
057     * entry that has been joined with a search result entry using the LDAP join
058     * control.  See the class-level documentation for the
059     * {@link JoinRequestControl} class for additional information and an example
060     * demonstrating its use.
061     * <BR><BR>
062     * Joined entries are encoded as follows:
063     * <PRE>
064     *   JoinedEntry ::= SEQUENCE {
065     *        objectName            LDAPDN,
066     *        attributes            PartialAttributeList,
067     *        nestedJoinResults     SEQUENCE OF JoinedEntry OPTIONAL }
068     * </PRE>
069     */
070    @NotMutable()
071    @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
072    public final class JoinedEntry
073           extends ReadOnlyEntry
074    {
075      /**
076       * The serial version UID for this serializable class.
077       */
078      private static final long serialVersionUID = -6519864521813773703L;
079    
080    
081    
082      // The list of nested join results for this joined entry.
083      private final List<JoinedEntry> nestedJoinResults;
084    
085    
086    
087      /**
088       * Creates a new joined entry with the specified DN, attributes, and nested
089       * join results.
090       *
091       * @param  entry              The entry containing the DN and attributes to
092       *                            use for this joined entry.  It must not be
093       *                            {@code null}.
094       * @param  nestedJoinResults  A list of nested join results for this joined
095       *                            entry.  It may be {@code null} or empty if there
096       *                            are no nested join results.
097       */
098      public JoinedEntry(final Entry entry,
099                         final List<JoinedEntry> nestedJoinResults)
100      {
101        this(entry.getDN(), entry.getAttributes(), nestedJoinResults);
102      }
103    
104    
105    
106      /**
107       * Creates a new joined entry with the specified DN, attributes, and nested
108       * join results.
109       *
110       * @param  dn                 The DN for this joined entry.  It must not be
111       *                            {@code null}.
112       * @param  attributes         The set of attributes for this joined entry.  It
113       *                            must not be {@code null}.
114       * @param  nestedJoinResults  A list of nested join results for this joined
115       *                            entry.  It may be {@code null} or empty if there
116       *                            are no nested join results.
117       */
118      public JoinedEntry(final String dn, final Collection<Attribute> attributes,
119                         final List<JoinedEntry> nestedJoinResults)
120      {
121        super(dn, attributes);
122    
123        if (nestedJoinResults == null)
124        {
125          this.nestedJoinResults = Collections.emptyList();
126        }
127        else
128        {
129          this.nestedJoinResults = Collections.unmodifiableList(nestedJoinResults);
130        }
131      }
132    
133    
134    
135      /**
136       * Encodes this joined entry to an ASN.1 element.
137       *
138       * @return  An ASN.1 element containing the encoded representation of this
139       *          joined entry.
140       */
141      ASN1Element encode()
142      {
143        final ArrayList<ASN1Element> elements = new ArrayList<ASN1Element>(3);
144    
145        elements.add(new ASN1OctetString(getDN()));
146    
147        final ArrayList<ASN1Element> attrElements = new ArrayList<ASN1Element>();
148        for (final Attribute a : getAttributes())
149        {
150          attrElements.add(a.encode());
151        }
152        elements.add(new ASN1Sequence(attrElements));
153    
154        if (! nestedJoinResults.isEmpty())
155        {
156          final ArrayList<ASN1Element> nestedElements =
157               new ArrayList<ASN1Element>(nestedJoinResults.size());
158          for (final JoinedEntry je : nestedJoinResults)
159          {
160            nestedElements.add(je.encode());
161          }
162          elements.add(new ASN1Sequence(nestedElements));
163        }
164    
165        return new ASN1Sequence(elements);
166      }
167    
168    
169    
170      /**
171       * Decodes the provided ASN.1 element as a joined entry.
172       *
173       * @param  element  The ASN.1 element to decode as a joined entry.
174       *
175       * @return  The decoded joined entry.
176       *
177       * @throws  LDAPException  If a problem occurs while attempting to decode the
178       *                         provided ASN.1 element as a joined entry.
179       */
180      static JoinedEntry decode(final ASN1Element element)
181             throws LDAPException
182      {
183        try
184        {
185          final ASN1Element[] elements =
186               ASN1Sequence.decodeAsSequence(element).elements();
187          final String dn =
188               ASN1OctetString.decodeAsOctetString(elements[0]).stringValue();
189    
190          final ASN1Element[] attrElements =
191               ASN1Sequence.decodeAsSequence(elements[1]).elements();
192          final ArrayList<Attribute> attrs =
193               new ArrayList<Attribute>(attrElements.length);
194          for (final ASN1Element e : attrElements)
195          {
196            attrs.add(Attribute.decode(ASN1Sequence.decodeAsSequence(e)));
197          }
198    
199          final ArrayList<JoinedEntry> nestedJoinResults;
200          if (elements.length == 3)
201          {
202            final ASN1Element[] nestedElements =
203                 ASN1Sequence.decodeAsSequence(elements[2]).elements();
204            nestedJoinResults = new ArrayList<JoinedEntry>(nestedElements.length);
205            for (final ASN1Element e : nestedElements)
206            {
207              nestedJoinResults.add(decode(e));
208            }
209          }
210          else
211          {
212            nestedJoinResults = new ArrayList<JoinedEntry>(0);
213          }
214    
215          return new JoinedEntry(dn, attrs, nestedJoinResults);
216        }
217        catch (Exception e)
218        {
219          debugException(e);
220    
221          throw new LDAPException(ResultCode.DECODING_ERROR,
222               ERR_JOINED_ENTRY_CANNOT_DECODE.get(getExceptionMessage(e)), e);
223        }
224      }
225    
226    
227    
228      /**
229       * Retrieves the list of nested join results for this joined entry.
230       *
231       * @return  The list of nested join results for this joined entry, or an
232       *          empty list if there are none.
233       */
234      public List<JoinedEntry> getNestedJoinResults()
235      {
236        return nestedJoinResults;
237      }
238    
239    
240    
241      /**
242       * Appends a string representation of this joined entry to the provided
243       * buffer.
244       *
245       * @param  buffer  The buffer to which the information should be appended.
246       */
247      @Override()
248      public void toString(final StringBuilder buffer)
249      {
250        buffer.append("JoinedEntry(dn='");
251        buffer.append(getDN());
252        buffer.append("', attributes={");
253    
254        final Iterator<Attribute> attrIterator = getAttributes().iterator();
255        while (attrIterator.hasNext())
256        {
257          attrIterator.next().toString(buffer);
258          if (attrIterator.hasNext())
259          {
260            buffer.append(", ");
261          }
262        }
263    
264        buffer.append("}, nestedJoinResults={");
265    
266        final Iterator<JoinedEntry> entryIterator = nestedJoinResults.iterator();
267        while (entryIterator.hasNext())
268        {
269          entryIterator.next().toString(buffer);
270          if (entryIterator.hasNext())
271          {
272            buffer.append(", ");
273          }
274        }
275    
276        buffer.append("})");
277      }
278    }