001/*
002 * Copyright 2009-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2009-2024 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2009-2024 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.ldap.sdk.unboundidds.controls;
037
038
039
040import java.util.ArrayList;
041import java.util.LinkedHashMap;
042import java.util.List;
043import java.util.Map;
044
045import com.unboundid.asn1.ASN1Element;
046import com.unboundid.asn1.ASN1OctetString;
047import com.unboundid.ldap.sdk.Control;
048import com.unboundid.ldap.sdk.DereferencePolicy;
049import com.unboundid.ldap.sdk.Filter;
050import com.unboundid.ldap.sdk.JSONControlDecodeHelper;
051import com.unboundid.ldap.sdk.LDAPException;
052import com.unboundid.ldap.sdk.ResultCode;
053import com.unboundid.ldap.sdk.SearchScope;
054import com.unboundid.util.Debug;
055import com.unboundid.util.NotMutable;
056import com.unboundid.util.NotNull;
057import com.unboundid.util.StaticUtils;
058import com.unboundid.util.ThreadSafety;
059import com.unboundid.util.ThreadSafetyLevel;
060import com.unboundid.util.json.JSONArray;
061import com.unboundid.util.json.JSONBoolean;
062import com.unboundid.util.json.JSONField;
063import com.unboundid.util.json.JSONNumber;
064import com.unboundid.util.json.JSONObject;
065import com.unboundid.util.json.JSONString;
066import com.unboundid.util.json.JSONValue;
067
068import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*;
069
070
071
072/**
073 * This class provides an implementation of an LDAP control which can be
074 * included in a search request to indicate that search result entries should be
075 * returned along with related entries based on a given set of criteria, much
076 * like an SQL join in a relational database.
077 * <BR>
078 * <BLOCKQUOTE>
079 *   <B>NOTE:</B>  This class, and other classes within the
080 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
081 *   supported for use against Ping Identity, UnboundID, and
082 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
083 *   for proprietary functionality or for external specifications that are not
084 *   considered stable or mature enough to be guaranteed to work in an
085 *   interoperable way with other types of LDAP servers.
086 * </BLOCKQUOTE>
087 * <BR>
088 * This request control has an OID of 1.3.6.1.4.1.30221.2.5.9, and the
089 * criticality is generally true.  It must have a value, and the format of that
090 * value is described in the class-level documentation for the
091 * {@link JoinRequestValue} class.
092 * <BR>
093 * <H2>Example</H2>
094 * Consider the case in which user entries include an account number, but
095 * additional information about those accounts are available in separate
096 * entries.    If you wish to retrieve both the user and account entries for a
097 * user given only a user ID, then you may accomplish that using the join
098 * request control as follows:
099 * <PRE>
100 * SearchRequest searchRequest = new SearchRequest(
101 *      "ou=People,dc=example,dc=com", SearchScope.SUB,
102 *      Filter.createEqualityFilter("uid", userID));
103 * searchRequest.addControl(new JoinRequestControl(new JoinRequestValue(
104 *      JoinRule.createEqualityJoin("accountNumber", "accountNumber", false),
105 *      JoinBaseDN.createUseCustomBaseDN("ou=Accounts,dc=example,dc=com"),
106 *      SearchScope.SUB, DereferencePolicy.NEVER, null,
107 *      Filter.createEqualityFilter("objectClass", "accountEntry"),
108 *      new String[0], false, null)));
109 * SearchResult searchResult = connection.search(searchRequest);
110 *
111 * for (SearchResultEntry userEntry : searchResult.getSearchEntries())
112 * {
113 *   JoinResultControl c = JoinResultControl.get(userEntry);
114 *   for (JoinedEntry accountEntry : c.getJoinResults())
115 *   {
116 *     // User userEntry was joined with account accountEntry
117 *   }
118 * }
119 * </PRE>
120 */
121@NotMutable()
122@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
123public final class JoinRequestControl
124       extends Control
125{
126  /**
127   * The OID (1.3.6.1.4.1.30221.2.5.9) for the join request control.
128   */
129  @NotNull public static final String JOIN_REQUEST_OID =
130       "1.3.6.1.4.1.30221.2.5.9";
131
132
133
134  /**
135   * The name of the field used to hold the alias dereferencing behavior in the
136   * JSON representation of this control.
137   */
138  @NotNull private static final String JSON_FIELD_ALIAS_DEREFERENCING_BEHAVIOR =
139       "alias-dereferencing-behavior";
140
141
142
143  /**
144   * The name of the field used to hold the requested attributes in the JSON
145   * representation of this control.
146   */
147  @NotNull private static final String JSON_FIELD_ATTRIBUTES = "attributes";
148
149
150
151  /**
152   * The name of the field used to hold the base DN type in the JSON
153   * representation of this control.
154   */
155  @NotNull private static final String JSON_FIELD_BASE_DN_TYPE = "base-dn-type";
156
157
158
159  /**
160   * The name of the field used to hold the base DN value in the JSON
161   * representation of this control.
162   */
163  @NotNull private static final String JSON_FIELD_BASE_DN_VALUE =
164       "base-dn-value";
165
166
167
168  /**
169   * The name of the field used to hold the filter in the JSON representation
170   * of this control.
171   */
172  @NotNull private static final String JSON_FIELD_FILTER = "filter";
173
174
175
176  /**
177   * The name of the field used to hold the join rule in the JSON representation
178   * of this control.
179   */
180  @NotNull private static final String JSON_FIELD_JOIN_RULE = "join-rule";
181
182
183
184  /**
185   * The name of the field used to hold a nested join value in the JSON
186   * representation of this control.
187   */
188  @NotNull private static final String JSON_FIELD_NESTED_JOIN = "nested-join";
189
190
191
192  /**
193   * The name of the field used to hold the require-match flag in the JSON
194   * representation of this control.
195   */
196  @NotNull private static final String JSON_FIELD_REQUIRE_MATCH =
197       "require-match";
198
199
200
201  /**
202   * The name of the field used to hold the scope in the JSON representation of
203   * this control.
204   */
205  @NotNull private static final String JSON_FIELD_SCOPE = "scope";
206
207
208
209  /**
210   * The name of the field used to hold the size limit in the JSON
211   * representation of this control.
212   */
213  @NotNull private static final String JSON_FIELD_SIZE_LIMIT = "size-limit";
214
215
216
217  /**
218   * The neverDerefAliases alias dereferencing behavior that will be used in the
219   * JSON representation of this control.
220   */
221  @NotNull private static final String JSON_ALIAS_BEHAVIOR_ALWAYS =
222       "derefAlways";
223
224
225
226  /**
227   * The neverDerefAliases alias dereferencing behavior that will be used in the
228   * JSON representation of this control.
229   */
230  @NotNull private static final String JSON_ALIAS_BEHAVIOR_FINDING =
231       "derefInFindingBaseObj";
232
233
234
235  /**
236   * The neverDerefAliases alias dereferencing behavior that will be used in the
237   * JSON representation of this control.
238   */
239  @NotNull private static final String JSON_ALIAS_BEHAVIOR_NEVER =
240       "neverDerefAliases";
241
242
243
244  /**
245   * The neverDerefAliases alias dereferencing behavior that will be used in the
246   * JSON representation of this control.
247   */
248  @NotNull private static final String JSON_ALIAS_BEHAVIOR_SEARCHING =
249       "derefInSearching";
250
251
252
253  /**
254   * The base DN type value that will indicate that a custom base DN should be
255   * used as the join base DN in the JSON representation of this control.
256   */
257  @NotNull private static final String JSON_BASE_DN_TYPE_USE_CUSTOM_BASE_DN =
258       "use-custom-base-dn";
259
260
261
262  /**
263   * The base DN type value that will indicate that the search base DN should
264   * be used as the join base DN in the JSON representation of this control.
265   */
266  @NotNull private static final String JSON_BASE_DN_TYPE_USE_SEARCH_BASE_DN =
267       "use-search-base-dn";
268
269
270
271  /**
272   * The base DN type value that will indicate that the source entry DN should
273   * be used as the join base DN in the JSON representation of this control.
274   */
275  @NotNull private static final String JSON_BASE_DN_TYPE_USE_SOURCE_ENTRY_DN =
276       "use-source-entry-dn";
277
278
279
280  /**
281   * The baseObject scope value that will be used in the JSON representation of
282   * this control.
283   */
284  @NotNull private static final String JSON_SCOPE_BASE_OBJECT = "baseObject";
285
286
287
288  /**
289   * The singleLevel scope value that will be used in the JSON representation of
290   * this control.
291   */
292  @NotNull private static final String JSON_SCOPE_SINGLE_LEVEL = "singleLevel";
293
294
295
296  /**
297   * The subordinateSubtree scope value that will be used in the JSON
298   * representation of this control.
299   */
300  @NotNull private static final String JSON_SCOPE_SUBORDINATE_SUBTREE =
301       "subordinateSubtree";
302
303
304
305  /**
306   * The wholeSubtree scope value that will be used in the JSON representation
307   * of this control.
308   */
309  @NotNull private static final String JSON_SCOPE_WHOLE_SUBTREE =
310       "wholeSubtree";
311
312
313
314  /**
315   * The serial version UID for this serializable class.
316   */
317  private static final long serialVersionUID = -1321645105838145996L;
318
319
320
321  // The join request value for this control.
322  @NotNull private final JoinRequestValue joinRequestValue;
323
324
325
326  /**
327   * Creates a new join request control with the provided join request value.
328   *
329   * @param  joinRequestValue  The join request value to use for this control.
330   */
331  public JoinRequestControl(@NotNull final JoinRequestValue joinRequestValue)
332  {
333    this(true, joinRequestValue);
334  }
335
336
337
338  /**
339   * Creates a new join request control with the provided join request value.
340   *
341   * @param  isCritical        Indicates whether this control should be
342   *                           considered critical.
343   * @param  joinRequestValue  The join request value to use for this control.
344   */
345  public JoinRequestControl(final boolean isCritical,
346                            @NotNull final JoinRequestValue joinRequestValue)
347  {
348    super(JOIN_REQUEST_OID, isCritical,
349          new ASN1OctetString(joinRequestValue.encode().encode()));
350
351    this.joinRequestValue = joinRequestValue;
352  }
353
354
355
356  /**
357   * Creates a new join request control which is decoded from the provided
358   * generic control.
359   *
360   * @param  control  The generic control to be decoded as a join request
361   *                  control.
362   *
363   * @throws  LDAPException  If the provided control cannot be decoded as a
364   *                         virtual attributes only request control.
365   */
366  public JoinRequestControl(@NotNull final Control control)
367         throws LDAPException
368  {
369    super(control);
370
371    final ASN1OctetString value = control.getValue();
372    if (value == null)
373    {
374      throw new LDAPException(ResultCode.DECODING_ERROR,
375           ERR_JOIN_REQUEST_CONTROL_NO_VALUE.get());
376    }
377
378    final ASN1Element valueElement;
379    try
380    {
381      valueElement = ASN1Element.decode(value.getValue());
382    }
383    catch (final Exception e)
384    {
385      Debug.debugException(e);
386
387      throw new LDAPException(ResultCode.DECODING_ERROR,
388           ERR_JOIN_REQUEST_VALUE_CANNOT_DECODE.get(
389                StaticUtils.getExceptionMessage(e)),
390           e);
391    }
392
393    joinRequestValue = JoinRequestValue.decode(valueElement);
394  }
395
396
397
398  /**
399   * Retrieves the join request value for this join request control.
400   *
401   * @return  The join request value for this join request control.
402   */
403  @NotNull()
404  public JoinRequestValue getJoinRequestValue()
405  {
406    return joinRequestValue;
407  }
408
409
410
411  /**
412   * {@inheritDoc}
413   */
414  @Override()
415  @NotNull()
416  public String getControlName()
417  {
418    return INFO_CONTROL_NAME_JOIN_REQUEST.get();
419  }
420
421
422
423  /**
424   * Retrieves a representation of this join request control as a JSON object.
425   * The JSON object uses the following fields:
426   * <UL>
427   *   <LI>
428   *     {@code oid} -- A mandatory string field whose value is the object
429   *     identifier for this control.  For the join request control, the OID is
430   *     "1.3.6.1.4.1.30221.2.5.9".
431   *   </LI>
432   *   <LI>
433   *     {@code control-name} -- An optional string field whose value is a
434   *     human-readable name for this control.  This field is only intended for
435   *     descriptive purposes, and when decoding a control, the {@code oid}
436   *     field should be used to identify the type of control.
437   *   </LI>
438   *   <LI>
439   *     {@code criticality} -- A mandatory Boolean field used to indicate
440   *     whether this control is considered critical.
441   *   </LI>
442   *   <LI>
443   *     {@code value-base64} -- An optional string field whose value is a
444   *     base64-encoded representation of the raw value for this join request
445   *     control.  Exactly one of the {@code value-base64} and
446   *     {@code value-json} fields must be present.
447   *   </LI>
448   *   <LI>
449   *     {@code value-json} -- An optional JSON object field whose value is a
450   *     user-friendly representation of the value for this join request
451   *     control.  Exactly one of the {@code value-base64} and
452   *     {@code value-json} fields must be present, and if the
453   *     {@code value-json} field is used, then it will use the following
454   *     fields:
455   *     <UL>
456   *       <LI>
457   *         {@code join-rule} -- A mandatory JSON object field that provides
458   *         the primary criteria to use when selecting entries to be joined
459   *         with search result entries.  The format for join rule objects will
460   *         be described in more detail below.
461   *       </LI>
462   *       <LI>
463   *         {@code base-dn-type} -- A mandatory string field that indicates
464   *         who the server should determine the base DN to use for join
465   *         processing.  The value must be one of "{@code use-search-base-dn}",
466   *         "{@code use-source-entry-dn}", or "{@code use-custom-base-dn}".
467   *       </LI>
468   *       <LI>
469   *         {@code base-dn-value} -- An optional string field that provides the
470   *         custom base DN value to use if the {@code base-dn-type} value was
471   *         "{@code use-custom-base-dn}".  This field must be present if the
472   *         {@code base-dn-type} value was "{@code use-custom-base-dn}", and it
473   *         must be absent for other {@code base-dn-type} values.
474   *       </LI>
475   *       <LI>
476   *         {@code scope} -- An optional string field whose value specifies the
477   *         scope to use for join processing.  If present, the value must be
478   *         one of "{@code baseObject}", "{@code singleLevel}",
479   *         "{@code wholeSubtree}", or "{@code subordinateSubtree}".  If this
480   *         is not specified, the scope from the search request will be used.
481   *       </LI>
482   *       <LI>
483   *         {@code alias-dereferencing-behavior} -- An optional string field
484   *         whose value specifies the behavior to use for dereferencing any
485   *         aliases encountered during join processing.  If present, the value
486   *         must be one of "{@code neverDerefAliases}",
487   *         "{@code derefInSearching}", "{@code derefInFindingBaseObj}", or
488   *         {@code derefAlways}".  If this is not specified, the dereferencing
489   *         behavior from the search request will be used.
490   *       </LI>
491   *       <LI>
492   *         {@code size-limit} -- An optional integer field whose value
493   *         specifies the maximum number of entries that may be joined with any
494   *         single search
495   *         result entry.
496   *       </LI>
497   *       <LI>
498   *         {@code filter} -- An optional string field whose value is the
499   *         string representation of a filter that will be required to match an
500   *         entry for it to be joined with a search result entry.
501   *       </LI>
502   *       <LI>
503   *         {@code attributes} -- An optional array field whose values are
504   *         strings that are the names of attributes to include in joined
505   *         entries.
506   *       </LI>
507   *       <LI>
508   *         {@code require-match} -- A mandatory Boolean field that indicates
509   *         whether to suppress a search result entry from the set of results
510   *         to the client if it is not joined with any other entries.
511   *       </LI>
512   *       <LI>
513   *         {@code nested-join} -- An optional JSON object field whose value
514   *         represents the join criteria for a nested join operation.  If
515   *         present, the fields in this object are the same as the fields that
516   *         may be used in the top-level {@code value-json} field (optionally
517   *         including another {@code nested-jon} element if desired).
518   *       </LI>
519   *     </UL>
520   *   </LI>
521   * </UL>
522   * <BR><BR>
523   * The following encodings may be used for different types of join rules:
524   * <UL>
525   *   <LI>
526   *     The fields that may be used for a DN join include:
527   *     <UL>
528   *       <LI>
529   *         {@code type} -- A mandatory string field whose value must be
530   *         "{@code dn}".
531   *       </LI>
532   *       <LI>
533   *         {@code source-attribute} -- A mandatory string field whose value is
534   *         the name of the attribute in the source entry that contains the DNs
535   *         of the entries to be joined with that entry.
536   *       </LI>
537   *     </UL>
538   *   </LI>
539   *   <LI>
540   *     The fields that may be used for a reverse DN join include:
541   *     <UL>
542   *       <LI>
543   *         {@code type} -- A mandatory string field whose value must be
544   *         "{@code reverse-dn}".
545   *       </LI>
546   *       <LI>
547   *         {@code target-attribute} -- A mandatory string field whose value is
548   *         the name of the attribute in joined entries that contains a value
549   *         that matches the DN of the source entry.
550   *       </LI>
551   *     </UL>
552   *   </LI>
553   *   <LI>
554   *     The fields that may be used for an equality join include:
555   *     <UL>
556   *       <LI>
557   *         {@code type} -- A mandatory string field whose value must be
558   *         "{@code equality}".
559   *       </LI>
560   *       <LI>
561   *         {@code source-attribute} -- A mandatory string field whose value is
562   *         the name of an attribute in the source entry whose values will be
563   *         used to identify entries to be joined with that source entry.
564   *       </LI>
565   *       <LI>
566   *         {@code target-attribute} -- A mandatory string field whose value is
567   *         the name of the attribute in joined entries that must contain at
568   *         least one of the values from the source attribute in the source
569   *         entry.
570   *       </LI>
571   *       <LI>
572   *         {@code match-all} -- A mandatory Boolean field that indicates
573   *         whether to only join entries in which the target attribute contains
574   *         all of the values of the source attribute.
575   *       </LI>
576   *     </UL>
577   *   </LI>
578   *   <LI>
579   *     The fields that may be used for a contains join include:
580   *     <UL>
581   *       <LI>
582   *         {@code type} -- A mandatory string field whose value must be
583   *         "{@code contains}".
584   *       </LI>
585   *       <LI>
586   *         {@code source-attribute} -- A mandatory string field whose value is
587   *         the name of an attribute in the source entry whose values will be
588   *         used to identify entries to be joined with that source entry.
589   *       </LI>
590   *       <LI>
591   *         {@code target-attribute} -- A mandatory string field whose value is
592   *         the name of the attribute in joined entries that must contain at
593   *         least one value that includes the value of a source attribute as a
594   *         substring.
595   *       </LI>
596   *       <LI>
597   *         {@code match-all} -- A mandatory Boolean field that indicates
598   *         whether to only join entries in which the target attribute contains
599   *         values that contain all of the values of the source attribute as
600   *         substrings.
601   *       </LI>
602   *     </UL>
603   *   </LI>
604   *   <LI>
605   *     The fields that may be used for an AND join include:
606   *     <UL>
607   *       <LI>
608   *         {@code type} -- A mandatory string field whose value must be
609   *         "{@code and}".
610   *       </LI>
611   *       <LI>
612   *         {@code rules} -- A mandatory, non-empty array field whose values
613   *         are the JSON objects that represent the nested join rules that must
614   *         all match an entry for it to be joined with the source entry.
615   *       </LI>
616   *     </UL>
617   *   </LI>
618   *   <LI>
619   *     The fields that may be used for an OR join include:
620   *     <UL>
621   *       <LI>
622   *         {@code type} -- A mandatory string field whose value must be
623   *         "{@code or}".
624   *       </LI>
625   *       <LI>
626   *         {@code rules} -- A mandatory, non-empty array field whose values
627   *         are the JSON objects that represent the nested join rules of which
628   *         at least one must match an entry for it to be joined with the
629   *         source entry.
630   *       </LI>
631   *     </UL>
632   *   </LI>
633   * </UL>
634   *
635   * @return  A JSON object that contains a representation of this control.
636   */
637  @Override()
638  @NotNull()
639  public JSONObject toJSONControl()
640  {
641    return new JSONObject(
642         new JSONField(JSONControlDecodeHelper.JSON_FIELD_OID,
643              JOIN_REQUEST_OID),
644         new JSONField(JSONControlDecodeHelper.JSON_FIELD_CONTROL_NAME,
645              INFO_CONTROL_NAME_JOIN_REQUEST.get()),
646         new JSONField(JSONControlDecodeHelper.JSON_FIELD_CRITICALITY,
647              isCritical()),
648         new JSONField(JSONControlDecodeHelper.JSON_FIELD_VALUE_JSON,
649              encodeValueJSON(joinRequestValue)));
650  }
651
652
653
654  /**
655   * Encodes the provided join request value to a JSON object.
656   *
657   * @param  value  The join request value to encode.  It must not be
658   *                {@code null}.
659   *
660   * @return  The JSON object containing the encoded join request value.
661   */
662  @NotNull()
663  private static JSONObject encodeValueJSON(
664               @NotNull final JoinRequestValue value)
665  {
666    final Map<String,JSONValue> fields = new LinkedHashMap<>();
667    fields.put(JSON_FIELD_JOIN_RULE, value.getJoinRule().toJSON());
668
669
670    final JoinBaseDN joinBaseDN = value.getBaseDN();
671    switch (joinBaseDN.getType())
672    {
673      case JoinBaseDN.BASE_TYPE_SEARCH_BASE:
674        fields.put(JSON_FIELD_BASE_DN_TYPE,
675             new JSONString(JSON_BASE_DN_TYPE_USE_SEARCH_BASE_DN));
676        break;
677
678      case JoinBaseDN.BASE_TYPE_SOURCE_ENTRY_DN:
679        fields.put(JSON_FIELD_BASE_DN_TYPE,
680             new JSONString(JSON_BASE_DN_TYPE_USE_SOURCE_ENTRY_DN));
681        break;
682
683      case JoinBaseDN.BASE_TYPE_CUSTOM:
684        fields.put(JSON_FIELD_BASE_DN_TYPE,
685             new JSONString(JSON_BASE_DN_TYPE_USE_CUSTOM_BASE_DN));
686        fields.put(JSON_FIELD_BASE_DN_VALUE,
687             new JSONString(joinBaseDN.getCustomBaseDN()));
688        break;
689    }
690
691
692    final SearchScope scope = value.getScope();
693    if (scope != null)
694    {
695      switch (scope.intValue())
696      {
697        case SearchScope.BASE_INT_VALUE:
698          fields.put(JSON_FIELD_SCOPE,
699               new JSONString(JSON_SCOPE_BASE_OBJECT));
700          break;
701
702        case SearchScope.ONE_INT_VALUE:
703          fields.put(JSON_FIELD_SCOPE,
704               new JSONString(JSON_SCOPE_SINGLE_LEVEL));
705          break;
706
707        case SearchScope.SUB_INT_VALUE:
708          fields.put(JSON_FIELD_SCOPE,
709               new JSONString(JSON_SCOPE_WHOLE_SUBTREE));
710          break;
711
712        case SearchScope.SUBORDINATE_SUBTREE_INT_VALUE:
713          fields.put(JSON_FIELD_SCOPE,
714               new JSONString(JSON_SCOPE_SUBORDINATE_SUBTREE));
715          break;
716      }
717    }
718
719
720    final DereferencePolicy derefPolicy = value.getDerefPolicy();
721    if (derefPolicy != null)
722    {
723      switch(derefPolicy.intValue())
724      {
725        case 0:
726          fields.put(JSON_FIELD_ALIAS_DEREFERENCING_BEHAVIOR,
727               new JSONString(JSON_ALIAS_BEHAVIOR_NEVER));
728          break;
729        case 1:
730          fields.put(JSON_FIELD_ALIAS_DEREFERENCING_BEHAVIOR,
731               new JSONString(JSON_ALIAS_BEHAVIOR_SEARCHING));
732          break;
733        case 2:
734          fields.put(JSON_FIELD_ALIAS_DEREFERENCING_BEHAVIOR,
735               new JSONString(JSON_ALIAS_BEHAVIOR_FINDING));
736          break;
737        case 3:
738          fields.put(JSON_FIELD_ALIAS_DEREFERENCING_BEHAVIOR,
739               new JSONString(JSON_ALIAS_BEHAVIOR_ALWAYS));
740          break;
741      }
742    }
743
744
745    final Integer sizeLimit = value.getSizeLimit();
746    if (sizeLimit != null)
747    {
748      fields.put(JSON_FIELD_SIZE_LIMIT, new JSONNumber(sizeLimit));
749    }
750
751
752    final Filter filter = value.getFilter();
753    if (filter != null)
754    {
755      fields.put(JSON_FIELD_FILTER, new JSONString(filter.toString()));
756    }
757
758
759    final String[] attributes = value.getAttributes();
760    if ((attributes != null) && (attributes.length > 0))
761    {
762      final List<JSONValue> attrValues = new ArrayList<>(attributes.length);
763      for (final String attr : attributes)
764      {
765        attrValues.add(new JSONString(attr));
766      }
767
768      fields.put(JSON_FIELD_ATTRIBUTES, new JSONArray(attrValues));
769    }
770
771
772    fields.put(JSON_FIELD_REQUIRE_MATCH,
773         new JSONBoolean(value.requireMatch()));
774
775
776    final JoinRequestValue nestedJoin = value.getNestedJoin();
777    if (nestedJoin != null)
778    {
779      fields.put(JSON_FIELD_NESTED_JOIN, encodeValueJSON(nestedJoin));
780    }
781
782    return new JSONObject(fields);
783  }
784
785
786
787  /**
788   * Attempts to decode the provided object as a JSON representation of a join
789   * request control.
790   *
791   * @param  controlObject  The JSON object to be decoded.  It must not be
792   *                        {@code null}.
793   * @param  strict         Indicates whether to use strict mode when decoding
794   *                        the provided JSON object.  If this is {@code true},
795   *                        then this method will throw an exception if the
796   *                        provided JSON object contains any unrecognized
797   *                        fields.  If this is {@code false}, then unrecognized
798   *                        fields will be ignored.
799   *
800   * @return  The join request control that was decoded from the provided JSON
801   *          object.
802   *
803   * @throws  LDAPException  If the provided JSON object cannot be parsed as a
804   *                         valid join request control.
805   */
806  @NotNull()
807  public static JoinRequestControl decodeJSONControl(
808              @NotNull final JSONObject controlObject,
809              final boolean strict)
810         throws LDAPException
811  {
812    final JSONControlDecodeHelper jsonControl = new JSONControlDecodeHelper(
813         controlObject, strict, true, true);
814
815    final ASN1OctetString rawValue = jsonControl.getRawValue();
816    if (rawValue != null)
817    {
818      return new JoinRequestControl(new Control(jsonControl.getOID(),
819           jsonControl.getCriticality(), rawValue));
820    }
821
822
823    final JoinRequestValue joinRequestValue =
824         decodeJoinRequestValueJSON(controlObject, jsonControl.getValueObject(),
825              strict);
826    return new JoinRequestControl(jsonControl.getCriticality(),
827         joinRequestValue);
828  }
829
830
831
832  /**
833   * Decodes the provided value object as a join request value.
834   *
835   * @param  controlObject  A JSON object that represents the join request
836   *                        control being decoded.  It must not be {@code null}.
837   * @param  valueObject    A JSON object that represents the join request value
838   *                        being decoded.  It must not be {@code null}.
839   * @param  strict         Indicates whether to use strict mode when decoding
840   *                        the provided JSON object.  If this is {@code true},
841   *                        then this method will throw an exception if the
842   *                        provided JSON object contains any unrecognized
843   *                        fields.  If this is {@code false}, then unrecognized
844   *                        fields will be ignored.
845   *
846   * @return  The join request value that was decoded.
847   *
848   * @throws  LDAPException  If the provided value object does not represent a
849   *                         valid join request control.
850   */
851  @NotNull()
852  private static JoinRequestValue decodeJoinRequestValueJSON(
853               @NotNull final JSONObject controlObject,
854               @NotNull final JSONObject valueObject,
855               final boolean strict)
856          throws LDAPException
857  {
858    final JSONObject joinRuleObject =
859         valueObject.getFieldAsObject(JSON_FIELD_JOIN_RULE);
860    if (joinRuleObject == null)
861    {
862      throw new LDAPException(ResultCode.DECODING_ERROR,
863           ERR_JOIN_REQUEST_JSON_MISSING_FIELD.get(
864                controlObject.toSingleLineString(),
865                JSON_FIELD_JOIN_RULE));
866    }
867
868    final JoinRule joinRule;
869    try
870    {
871      joinRule = JoinRule.decodeJSONJoinRule(joinRuleObject, strict);
872    }
873    catch (final LDAPException e)
874    {
875      Debug.debugException(e);
876      throw new LDAPException(ResultCode.DECODING_ERROR,
877           ERR_JOIN_REQUEST_JSON_INVALID_JOIN_RULE.get(
878                controlObject.toSingleLineString(),
879                JSON_FIELD_JOIN_RULE, e.getMessage()),
880           e);
881    }
882
883
884    final JoinBaseDN baseDN;
885    final String baseDNType =
886         valueObject.getFieldAsString(JSON_FIELD_BASE_DN_TYPE);
887    final String baseDNValue =
888         valueObject.getFieldAsString(JSON_FIELD_BASE_DN_VALUE);
889    if (baseDNType == null)
890    {
891      throw new LDAPException(ResultCode.DECODING_ERROR,
892           ERR_JOIN_REQUEST_JSON_MISSING_FIELD.get(
893                controlObject.toSingleLineString(),
894                JSON_FIELD_BASE_DN_TYPE));
895    }
896
897    switch (baseDNType)
898    {
899      case JSON_BASE_DN_TYPE_USE_SEARCH_BASE_DN:
900        if (baseDNValue != null)
901        {
902          throw new LDAPException(ResultCode.DECODING_ERROR,
903               ERR_JOIN_REQUEST_JSON_DISALLOWED_BASE_DN_VALUE.get(
904                    controlObject.toSingleLineString(),
905                    JSON_FIELD_BASE_DN_VALUE, JSON_FIELD_BASE_DN_TYPE,
906                    baseDNType));
907        }
908
909        baseDN = JoinBaseDN.createUseSearchBaseDN();
910        break;
911
912      case JSON_BASE_DN_TYPE_USE_SOURCE_ENTRY_DN:
913        if (baseDNValue != null)
914        {
915          throw new LDAPException(ResultCode.DECODING_ERROR,
916               ERR_JOIN_REQUEST_JSON_DISALLOWED_BASE_DN_VALUE.get(
917                    controlObject.toSingleLineString(),
918                    JSON_FIELD_BASE_DN_VALUE, JSON_FIELD_BASE_DN_TYPE,
919                    baseDNType));
920        }
921
922        baseDN = JoinBaseDN.createUseSourceEntryDN();
923        break;
924
925      case JSON_BASE_DN_TYPE_USE_CUSTOM_BASE_DN:
926        if (baseDNValue == null)
927        {
928          throw new LDAPException(ResultCode.DECODING_ERROR,
929               ERR_JOIN_REQUEST_JSON_MISSING_BASE_DN_VALUE.get(
930                    controlObject.toSingleLineString(),
931                    JSON_FIELD_BASE_DN_VALUE, JSON_FIELD_BASE_DN_TYPE,
932                    baseDNType));
933        }
934
935        baseDN = JoinBaseDN.createUseCustomBaseDN(baseDNValue);
936        break;
937
938      default:
939        throw new LDAPException(ResultCode.DECODING_ERROR,
940             ERR_JOIN_REQUEST_JSON_INVALID_BASE_DN_TYPE.get(
941                  controlObject.toSingleLineString(), baseDNType,
942                  JSON_FIELD_BASE_DN_TYPE, JSON_BASE_DN_TYPE_USE_SEARCH_BASE_DN,
943                  JSON_BASE_DN_TYPE_USE_SOURCE_ENTRY_DN,
944                  JSON_BASE_DN_TYPE_USE_CUSTOM_BASE_DN));
945    }
946
947
948    final SearchScope scope;
949    final String scopeStr =
950         valueObject.getFieldAsString(JSON_FIELD_SCOPE);
951    if (scopeStr == null)
952    {
953      scope = null;
954    }
955    else
956    {
957      switch (scopeStr)
958      {
959        case JSON_SCOPE_BASE_OBJECT:
960          scope = SearchScope.BASE;
961          break;
962        case JSON_SCOPE_SINGLE_LEVEL:
963          scope = SearchScope.ONE;
964          break;
965        case JSON_SCOPE_WHOLE_SUBTREE:
966          scope = SearchScope.SUB;
967          break;
968        case JSON_SCOPE_SUBORDINATE_SUBTREE:
969          scope = SearchScope.SUBORDINATE_SUBTREE;
970          break;
971        default:
972          throw new LDAPException(ResultCode.DECODING_ERROR,
973               ERR_JOIN_REQUEST_JSON_INVALID_SCOPE.get(
974                    controlObject.toSingleLineString(), scopeStr,
975                    JSON_FIELD_SCOPE, JSON_SCOPE_BASE_OBJECT,
976                    JSON_SCOPE_SINGLE_LEVEL, JSON_SCOPE_WHOLE_SUBTREE,
977                    JSON_SCOPE_SUBORDINATE_SUBTREE));
978      }
979    }
980
981
982    final DereferencePolicy derefPolicy;
983    final String derefStr =
984         valueObject.getFieldAsString(JSON_FIELD_ALIAS_DEREFERENCING_BEHAVIOR);
985    if (derefStr == null)
986    {
987      derefPolicy = null;
988    }
989    else
990    {
991      switch (derefStr)
992      {
993        case JSON_ALIAS_BEHAVIOR_NEVER:
994          derefPolicy = DereferencePolicy.NEVER;
995          break;
996        case JSON_ALIAS_BEHAVIOR_SEARCHING:
997          derefPolicy = DereferencePolicy.SEARCHING;
998          break;
999        case JSON_ALIAS_BEHAVIOR_FINDING:
1000          derefPolicy = DereferencePolicy.FINDING;
1001          break;
1002        case JSON_ALIAS_BEHAVIOR_ALWAYS:
1003          derefPolicy = DereferencePolicy.ALWAYS;
1004          break;
1005        default:
1006          throw new LDAPException(ResultCode.DECODING_ERROR,
1007               ERR_JOIN_REQUEST_JSON_INVALID_DEREF.get(
1008                    controlObject.toSingleLineString(), derefStr,
1009                    JSON_FIELD_ALIAS_DEREFERENCING_BEHAVIOR,
1010                    JSON_ALIAS_BEHAVIOR_NEVER, JSON_ALIAS_BEHAVIOR_SEARCHING,
1011                    JSON_ALIAS_BEHAVIOR_FINDING, JSON_ALIAS_BEHAVIOR_ALWAYS));
1012      }
1013    }
1014
1015
1016    final Integer sizeLimit =
1017         valueObject.getFieldAsInteger(JSON_FIELD_SIZE_LIMIT);
1018
1019
1020    final Filter filter;
1021    final String filterStr =  valueObject.getFieldAsString(JSON_FIELD_FILTER);
1022    if (filterStr == null)
1023    {
1024      filter = null;
1025    }
1026    else
1027    {
1028      try
1029      {
1030        filter = Filter.create(filterStr);
1031      }
1032      catch (final Exception e)
1033      {
1034        Debug.debugException(e);
1035        throw new LDAPException(ResultCode.DECODING_ERROR,
1036             ERR_JOIN_REQUEST_JSON_INVALID_FILTER.get(
1037                  controlObject.toSingleLineString(), filterStr,
1038                  JSON_FIELD_FILTER),
1039             e);
1040      }
1041    }
1042
1043
1044    final String[] attributes;
1045    final List<JSONValue> attrValues =
1046         valueObject.getFieldAsArray(JSON_FIELD_ATTRIBUTES);
1047    if (attrValues == null)
1048    {
1049      attributes = null;
1050    }
1051    else
1052    {
1053      attributes = new String[attrValues.size()];
1054      for (int i=0; i < attributes.length; i++)
1055      {
1056        final JSONValue v = attrValues.get(i);
1057        if (v instanceof JSONString)
1058        {
1059          attributes[i] = ((JSONString) v).stringValue();
1060        }
1061        else
1062        {
1063          throw new LDAPException(ResultCode.DECODING_ERROR,
1064               ERR_JOIN_REQUEST_JSON_ATTR_NOT_STRING.get(
1065                    controlObject.toSingleLineString(),
1066                    JSON_FIELD_ATTRIBUTES));
1067        }
1068      }
1069    }
1070
1071
1072    final Boolean requireMatch =
1073         valueObject.getFieldAsBoolean(JSON_FIELD_REQUIRE_MATCH);
1074    if (requireMatch == null)
1075    {
1076      throw new LDAPException(ResultCode.DECODING_ERROR,
1077           ERR_JOIN_REQUEST_JSON_MISSING_FIELD.get(
1078                controlObject.toSingleLineString(),
1079                JSON_FIELD_REQUIRE_MATCH));
1080    }
1081
1082
1083    final JoinRequestValue nestedJoin;
1084    final JSONObject nestedJoinObject =
1085         valueObject.getFieldAsObject(JSON_FIELD_NESTED_JOIN);
1086    if (nestedJoinObject == null)
1087    {
1088      nestedJoin = null;
1089    }
1090    else
1091    {
1092      nestedJoin =
1093           decodeJoinRequestValueJSON(controlObject, nestedJoinObject, strict);
1094    }
1095
1096
1097    if (strict)
1098    {
1099      final List<String> unrecognizedFields =
1100           JSONControlDecodeHelper.getControlObjectUnexpectedFields(
1101                valueObject, JSON_FIELD_JOIN_RULE, JSON_FIELD_BASE_DN_TYPE,
1102                JSON_FIELD_BASE_DN_VALUE, JSON_FIELD_SCOPE,
1103                JSON_FIELD_ALIAS_DEREFERENCING_BEHAVIOR, JSON_FIELD_SIZE_LIMIT,
1104                JSON_FIELD_FILTER, JSON_FIELD_ATTRIBUTES,
1105                JSON_FIELD_REQUIRE_MATCH, JSON_FIELD_NESTED_JOIN);
1106      if (! unrecognizedFields.isEmpty())
1107      {
1108        throw new LDAPException(ResultCode.DECODING_ERROR,
1109             ERR_JOIN_REQUEST_JSON_UNRECOGNIZED_FIELD.get(
1110                  controlObject.toSingleLineString(),
1111                  unrecognizedFields.get(0)));
1112      }
1113    }
1114
1115
1116    return new JoinRequestValue(joinRule, baseDN, scope, derefPolicy, sizeLimit,
1117    filter, attributes, requireMatch, nestedJoin);
1118  }
1119
1120
1121
1122  /**
1123   * {@inheritDoc}
1124   */
1125  @Override()
1126  public void toString(@NotNull final StringBuilder buffer)
1127  {
1128    buffer.append("JoinRequestControl(value=");
1129    joinRequestValue.toString(buffer);
1130    buffer.append(')');
1131  }
1132}