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.Collections;
027    import java.util.Iterator;
028    import java.util.List;
029    
030    import com.unboundid.asn1.ASN1Element;
031    import com.unboundid.asn1.ASN1Enumerated;
032    import com.unboundid.asn1.ASN1OctetString;
033    import com.unboundid.asn1.ASN1Sequence;
034    import com.unboundid.ldap.sdk.Control;
035    import com.unboundid.ldap.sdk.DecodeableControl;
036    import com.unboundid.ldap.sdk.LDAPException;
037    import com.unboundid.ldap.sdk.ResultCode;
038    import com.unboundid.ldap.sdk.SearchResultEntry;
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    import static com.unboundid.util.Validator.*;
047    
048    
049    
050    /**
051     * <BLOCKQUOTE>
052     *   <B>NOTE:</B>  This class is part of the Commercial Edition of the UnboundID
053     *   LDAP SDK for Java.  It is not available for use in applications that
054     *   include only the Standard Edition of the LDAP SDK, and is not supported for
055     *   use in conjunction with non-UnboundID products.
056     * </BLOCKQUOTE>
057     * This class provides an implementation of a control that may be included in a
058     * search result entry in response to a join request control to provide a set of
059     * entries related to the search result entry.    See the class-level
060     * documentation for the {@link JoinRequestControl} class for additional
061     * information and an example demonstrating its use.
062     * <BR><BR>
063     * The value of the join result control is encoded as follows:
064     * <PRE>
065     *   JoinResult ::= SEQUENCE {
066     *        COMPONENTS OF LDAPResult,
067     *        entries     [4] SEQUENCE OF JoinedEntry }
068     * </PRE>
069     */
070    @NotMutable()
071    @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
072    public final class JoinResultControl
073           extends Control
074           implements DecodeableControl
075    {
076      /**
077       * The OID (1.3.6.1.4.1.30221.2.5.9) for the join result control.
078       */
079      public static final String JOIN_RESULT_OID = "1.3.6.1.4.1.30221.2.5.9";
080    
081    
082    
083      /**
084       * The BER type for the referral URLs element.
085       */
086      private static final byte TYPE_REFERRAL_URLS = (byte) 0xA3;
087    
088    
089    
090      /**
091       * The BER type for the join results element.
092       */
093      private static final byte TYPE_JOIN_RESULTS = (byte) 0xA4;
094    
095    
096    
097      /**
098       * The serial version UID for this serializable class.
099       */
100      private static final long serialVersionUID = 681831114773253358L;
101    
102    
103    
104      // The set of entries which have been joined with the associated search result
105      // entry.
106      private final List<JoinedEntry> joinResults;
107    
108      // The set of referral URLs for this join result.
109      private final List<String> referralURLs;
110    
111      // The result code for this join result.
112      private final ResultCode resultCode;
113    
114      // The diagnostic message for this join result.
115      private final String diagnosticMessage;
116    
117      // The matched DN for this join result.
118      private final String matchedDN;
119    
120    
121    
122      /**
123       * Creates a new empty control instance that is intended to be used only for
124       * decoding controls via the {@code DecodeableControl} interface.
125       */
126      JoinResultControl()
127      {
128        resultCode        = null;
129        diagnosticMessage = null;
130        matchedDN         = null;
131        referralURLs      = null;
132        joinResults       = null;
133      }
134    
135    
136    
137      /**
138       * Creates a new join result control indicating a successful join.
139       *
140       * @param  joinResults  The set of entries that have been joined with the
141       *                      associated search result entry.  It may be
142       *                      {@code null} or empty if no entries were joined with
143       *                      the search result entry.
144       */
145      public JoinResultControl(final List<JoinedEntry> joinResults)
146      {
147        this(ResultCode.SUCCESS, null, null, null, joinResults);
148      }
149    
150    
151    
152      /**
153       * Creates a new join result control with the provided information.
154       *
155       * @param  resultCode         The result code for the join processing.  It
156       *                            must not be {@code null}.
157       * @param  diagnosticMessage  A message with additional information about the
158       *                            result of the join processing.  It may be
159       *                            {@code null} if no message is needed.
160       * @param  matchedDN          The matched DN for the join processing.  It may
161       *                            be {@code null} if no matched DN is needed.
162       * @param  referralURLs       The set of referral URLs for any referrals
163       *                            encountered while processing the join.  It may
164       *                            be {@code null} or empty if no referral URLs
165       *                            are needed.
166       * @param  joinResults        The set of entries that have been joined with
167       *                            associated search result entry.    It may be
168       *                            {@code null} or empty if no entries were joined
169       *                            with the search result entry.
170       */
171      public JoinResultControl(final ResultCode resultCode,
172                  final String diagnosticMessage, final String matchedDN,
173                  final List<String> referralURLs,
174                  final List<JoinedEntry> joinResults)
175      {
176        super(JOIN_RESULT_OID, false,
177              encodeValue(resultCode, diagnosticMessage, matchedDN, referralURLs,
178                          joinResults));
179    
180        this.resultCode        = resultCode;
181        this.diagnosticMessage = diagnosticMessage;
182        this.matchedDN         = matchedDN;
183    
184        if (referralURLs == null)
185        {
186          this.referralURLs = Collections.emptyList();
187        }
188        else
189        {
190          this.referralURLs = Collections.unmodifiableList(referralURLs);
191        }
192    
193        if (joinResults == null)
194        {
195          this.joinResults = Collections.emptyList();
196        }
197        else
198        {
199          this.joinResults = Collections.unmodifiableList(joinResults);
200        }
201      }
202    
203    
204    
205      /**
206       * Creates a new join result control with the provided information.
207       *
208       * @param  oid         The OID for the control.
209       * @param  isCritical  Indicates whether the control should be marked
210       *                     critical.
211       * @param  value       The encoded value for the control.  This may be
212       *                     {@code null} if no value was provided.
213       *
214       * @throws  LDAPException  If the provided control cannot be decoded as an
215       *                         account usable response control.
216       */
217      public JoinResultControl(final String oid, final boolean isCritical,
218                               final ASN1OctetString value)
219             throws LDAPException
220      {
221        super(oid, isCritical, value);
222    
223        if (value == null)
224        {
225          throw new LDAPException(ResultCode.DECODING_ERROR,
226               ERR_JOIN_RESULT_NO_VALUE.get());
227        }
228    
229        try
230        {
231          final ASN1Element valueElement = ASN1Element.decode(value.getValue());
232          final ASN1Element[] elements =
233               ASN1Sequence.decodeAsSequence(valueElement).elements();
234    
235          resultCode = ResultCode.valueOf(
236               ASN1Enumerated.decodeAsEnumerated(elements[0]).intValue());
237    
238          final String matchedDNStr =
239               ASN1OctetString.decodeAsOctetString(elements[1]).stringValue();
240          if (matchedDNStr.length() == 0)
241          {
242            matchedDN = null;
243          }
244          else
245          {
246            matchedDN = matchedDNStr;
247          }
248    
249          final String diagnosticMessageStr =
250               ASN1OctetString.decodeAsOctetString(elements[2]).stringValue();
251          if (diagnosticMessageStr.length() == 0)
252          {
253            diagnosticMessage = null;
254          }
255          else
256          {
257            diagnosticMessage = diagnosticMessageStr;
258          }
259    
260          final ArrayList<String>      refs    = new ArrayList<String>();
261          final ArrayList<JoinedEntry> entries = new ArrayList<JoinedEntry>();
262          for (int i=3; i < elements.length; i++)
263          {
264            switch (elements[i].getType())
265            {
266              case TYPE_REFERRAL_URLS:
267                final ASN1Element[] refElements =
268                     ASN1Sequence.decodeAsSequence(elements[i]).elements();
269                for (final ASN1Element e : refElements)
270                {
271                  refs.add(ASN1OctetString.decodeAsOctetString(e).stringValue());
272                }
273                break;
274    
275              case TYPE_JOIN_RESULTS:
276                final ASN1Element[] entryElements =
277                     ASN1Sequence.decodeAsSequence(elements[i]).elements();
278                for (final ASN1Element e : entryElements)
279                {
280                  entries.add(JoinedEntry.decode(e));
281                }
282                break;
283    
284              default:
285                throw new LDAPException(ResultCode.DECODING_ERROR,
286                     ERR_JOIN_RESULT_INVALID_ELEMENT_TYPE.get(
287                          toHex(elements[i].getType())));
288            }
289          }
290    
291          referralURLs = Collections.unmodifiableList(refs);
292          joinResults  = Collections.unmodifiableList(entries);
293        }
294        catch (Exception e)
295        {
296          debugException(e);
297    
298          throw new LDAPException(ResultCode.DECODING_ERROR,
299               ERR_JOIN_RESULT_CANNOT_DECODE.get(getExceptionMessage(e)), e);
300        }
301      }
302    
303    
304    
305      /**
306       * Encodes the provided information as appropriate for use as the value of
307       * this control.
308       *
309       * @param  resultCode         The result code for the join processing.  It
310       *                            must not be {@code null}.
311       * @param  diagnosticMessage  A message with additional information about the
312       *                            result of the join processing.  It may be
313       *                            {@code null} if no message is needed.
314       * @param  matchedDN          The matched DN for the join processing.  It may
315       *                            be {@code null} if no matched DN is needed.
316       * @param  referralURLs       The set of referral URLs for any referrals
317       *                            encountered while processing the join.  It may
318       *                            be {@code null} or empty if no referral URLs
319       *                            are needed.
320       * @param  joinResults        The set of entries that have been joined with
321       *                            associated search result entry.    It may be
322       *                            {@code null} or empty if no entries were joined
323       *                            with the search result entry.
324       *
325       * @return  An ASN.1 element containing an encoded representation of the
326       *          value for this control.
327       */
328      private static ASN1OctetString encodeValue(final ResultCode resultCode,
329                          final String diagnosticMessage, final String matchedDN,
330                          final List<String> referralURLs,
331                          final List<JoinedEntry> joinResults)
332      {
333        ensureNotNull(resultCode);
334    
335        final ArrayList<ASN1Element> elements = new ArrayList<ASN1Element>(5);
336        elements.add(new ASN1Enumerated(resultCode.intValue()));
337    
338        if (matchedDN == null)
339        {
340          elements.add(new ASN1OctetString());
341        }
342        else
343        {
344          elements.add(new ASN1OctetString(matchedDN));
345        }
346    
347        if (diagnosticMessage == null)
348        {
349          elements.add(new ASN1OctetString());
350        }
351        else
352        {
353          elements.add(new ASN1OctetString(diagnosticMessage));
354        }
355    
356        if ((referralURLs != null) && (! referralURLs.isEmpty()))
357        {
358          final ArrayList<ASN1Element> refElements =
359               new ArrayList<ASN1Element>(referralURLs.size());
360          for (final String s : referralURLs)
361          {
362            refElements.add(new ASN1OctetString(s));
363          }
364          elements.add(new ASN1Sequence(TYPE_REFERRAL_URLS, refElements));
365        }
366    
367        if ((joinResults == null) || joinResults.isEmpty())
368        {
369          elements.add(new ASN1Sequence(TYPE_JOIN_RESULTS));
370        }
371        else
372        {
373          final ArrayList<ASN1Element> entryElements =
374               new ArrayList<ASN1Element>(joinResults.size());
375          for (final JoinedEntry e : joinResults)
376          {
377            entryElements.add(e.encode());
378          }
379          elements.add(new ASN1Sequence(TYPE_JOIN_RESULTS, entryElements));
380        }
381    
382        return new ASN1OctetString(new ASN1Sequence(elements).encode());
383      }
384    
385    
386    
387      /**
388       * Retrieves the result code for this join result.
389       *
390       * @return  The result code for this join result.
391       */
392      public ResultCode getResultCode()
393      {
394        return resultCode;
395      }
396    
397    
398    
399      /**
400       * Retrieves the diagnostic message for this join result.
401       *
402       * @return  The diagnostic message for this join result, or {@code null} if
403       *          there is no diagnostic message.
404       */
405      public String getDiagnosticMessage()
406      {
407        return diagnosticMessage;
408      }
409    
410    
411    
412      /**
413       * Retrieves the matched DN for this join result.
414       *
415       * @return  The matched DN for this join result, or {@code null} if there is
416       *          no matched DN.
417       */
418      public String getMatchedDN()
419      {
420        return matchedDN;
421      }
422    
423    
424    
425      /**
426       * Retrieves the set of referral URLs for this join result.
427       *
428       * @return  The set of referral URLs for this join result, or an empty list
429       *          if there are no referral URLs.
430       */
431      public List<String> getReferralURLs()
432      {
433        return referralURLs;
434      }
435    
436    
437    
438      /**
439       * Retrieves the set of entries that have been joined with the associated
440       * search result entry.
441       *
442       * @return  The set of entries that have been joined with the associated
443       *          search result entry.
444       */
445      public List<JoinedEntry> getJoinResults()
446      {
447        return joinResults;
448      }
449    
450    
451    
452      /**
453       * {@inheritDoc}
454       */
455      public JoinResultControl decodeControl(final String oid,
456                                             final boolean isCritical,
457                                             final ASN1OctetString value)
458             throws LDAPException
459      {
460        return new JoinResultControl(oid, isCritical, value);
461      }
462    
463    
464    
465      /**
466       * Extracts a join result control from the provided search result entry.
467       *
468       * @param  entry  The search result entry from which to retrieve the join
469       *                result control.
470       *
471       * @return  The join result control contained in the provided search result
472       *          entry, or {@code null} if the entry did not contain a join result
473       *          control.
474       *
475       * @throws  LDAPException  If a problem is encountered while attempting to
476       *                         decode the join result control contained in the
477       *                         provided search result entry.
478       */
479      public static JoinResultControl get(final SearchResultEntry entry)
480             throws LDAPException
481      {
482        final Control c = entry.getControl(JOIN_RESULT_OID);
483        if (c == null)
484        {
485          return null;
486        }
487    
488        if (c instanceof JoinResultControl)
489        {
490          return (JoinResultControl) c;
491        }
492        else
493        {
494          return new JoinResultControl(c.getOID(), c.isCritical(), c.getValue());
495        }
496      }
497    
498    
499    
500      /**
501       * {@inheritDoc}
502       */
503      @Override()
504      public String getControlName()
505      {
506        return INFO_CONTROL_NAME_JOIN_RESULT.get();
507      }
508    
509    
510    
511      /**
512       * {@inheritDoc}
513       */
514      @Override()
515      public void toString(final StringBuilder buffer)
516      {
517        buffer.append("JoinResultControl(resultCode='");
518        buffer.append(resultCode.getName());
519        buffer.append("', diagnosticMessage='");
520    
521        if (diagnosticMessage != null)
522        {
523          buffer.append(diagnosticMessage);
524        }
525    
526        buffer.append("', matchedDN='");
527        if (matchedDN != null)
528        {
529          buffer.append(matchedDN);
530        }
531    
532        buffer.append("', referralURLs={");
533        final Iterator<String> refIterator = referralURLs.iterator();
534        while (refIterator.hasNext())
535        {
536          buffer.append(refIterator.next());
537          if (refIterator.hasNext())
538          {
539            buffer.append(", ");
540          }
541        }
542    
543        buffer.append("}, joinResults={");
544        final Iterator<JoinedEntry> entryIterator = joinResults.iterator();
545        while (entryIterator.hasNext())
546        {
547          entryIterator.next().toString(buffer);
548          if (entryIterator.hasNext())
549          {
550            buffer.append(", ");
551          }
552        }
553    
554        buffer.append("})");
555      }
556    }