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