001/*
002 * Copyright 2009-2022 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2009-2022 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-2022 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    super(JOIN_REQUEST_OID, true,
334          new ASN1OctetString(joinRequestValue.encode().encode()));
335
336    this.joinRequestValue = joinRequestValue;
337  }
338
339
340
341  /**
342   * Creates a new join request control which is decoded from the provided
343   * generic control.
344   *
345   * @param  control  The generic control to be decoded as a join request
346   *                  control.
347   *
348   * @throws  LDAPException  If the provided control cannot be decoded as a
349   *                         virtual attributes only request control.
350   */
351  public JoinRequestControl(@NotNull final Control control)
352         throws LDAPException
353  {
354    super(control);
355
356    final ASN1OctetString value = control.getValue();
357    if (value == null)
358    {
359      throw new LDAPException(ResultCode.DECODING_ERROR,
360           ERR_JOIN_REQUEST_CONTROL_NO_VALUE.get());
361    }
362
363    final ASN1Element valueElement;
364    try
365    {
366      valueElement = ASN1Element.decode(value.getValue());
367    }
368    catch (final Exception e)
369    {
370      Debug.debugException(e);
371
372      throw new LDAPException(ResultCode.DECODING_ERROR,
373           ERR_JOIN_REQUEST_VALUE_CANNOT_DECODE.get(
374                StaticUtils.getExceptionMessage(e)),
375           e);
376    }
377
378    joinRequestValue = JoinRequestValue.decode(valueElement);
379  }
380
381
382
383  /**
384   * Retrieves the join request value for this join request control.
385   *
386   * @return  The join request value for this join request control.
387   */
388  @NotNull()
389  public JoinRequestValue getJoinRequestValue()
390  {
391    return joinRequestValue;
392  }
393
394
395
396  /**
397   * {@inheritDoc}
398   */
399  @Override()
400  @NotNull()
401  public String getControlName()
402  {
403    return INFO_CONTROL_NAME_JOIN_REQUEST.get();
404  }
405
406
407
408  /**
409   * Retrieves a representation of this join request control as a JSON object.
410   * The JSON object uses the following fields:
411   * <UL>
412   *   <LI>
413   *     {@code oid} -- A mandatory string field whose value is the object
414   *     identifier for this control.  For the join request control, the OID is
415   *     "1.3.6.1.4.1.30221.2.5.9".
416   *   </LI>
417   *   <LI>
418   *     {@code control-name} -- An optional string field whose value is a
419   *     human-readable name for this control.  This field is only intended for
420   *     descriptive purposes, and when decoding a control, the {@code oid}
421   *     field should be used to identify the type of control.
422   *   </LI>
423   *   <LI>
424   *     {@code criticality} -- A mandatory Boolean field used to indicate
425   *     whether this control is considered critical.
426   *   </LI>
427   *   <LI>
428   *     {@code value-base64} -- An optional string field whose value is a
429   *     base64-encoded representation of the raw value for this join request
430   *     control.  Exactly one of the {@code value-base64} and
431   *     {@code value-json} fields must be present.
432   *   </LI>
433   *   <LI>
434   *     {@code value-json} -- An optional JSON object field whose value is a
435   *     user-friendly representation of the value for this join request
436   *     control.  Exactly one of the {@code value-base64} and
437   *     {@code value-json} fields must be present, and if the
438   *     {@code value-json} field is used, then it will use the following
439   *     fields:
440   *     <UL>
441   *       <LI>
442   *         {@code join-rule} -- A mandatory JSON object field that provides
443   *         the primary criteria to use when selecting entries to be joined
444   *         with search result entries.  The format for join rule objects will
445   *         be described in more detail below.
446   *       </LI>
447   *       <LI>
448   *         {@code base-dn-type} -- A mandatory string field that indicates
449   *         who the server should determine the base DN to use for join
450   *         processing.  The value must be one of "{@code use-search-base-dn}",
451   *         "{@code use-source-entry-dn}", or "{@code use-custom-base-dn}".
452   *       </LI>
453   *       <LI>
454   *         {@code base-dn-value} -- An optional string field that provides the
455   *         custom base DN value to use if the {@code base-dn-type} value was
456   *         "{@code use-custom-base-dn}".  This field must be present if the
457   *         {@code base-dn-type} value was "{@code use-custom-base-dn}", and it
458   *         must be absent for other {@code base-dn-type} values.
459   *       </LI>
460   *       <LI>
461   *         {@code scope} -- An optional string field whose value specifies the
462   *         scope to use for join processing.  If present, the value must be
463   *         one of "{@code baseObject}", "{@code singleLevel}",
464   *         "{@code wholeSubtree}", or "{@code subordinateSubtree}".  If this
465   *         is not specified, the scope from the search request will be used.
466   *       </LI>
467   *       <LI>
468   *         {@code alias-dereferencing-behavior} -- An optional string field
469   *         whose value specifies the behavior to use for dereferencing any
470   *         aliases encountered during join processing.  If present, the value
471   *         must be one of "{@code neverDerefAliases}",
472   *         "{@code derefInSearching}", "{@code derefInFindingBaseObj}", or
473   *         {@code derefAlways}".  If this is not specified, the dereferencing
474   *         behavior from the search request will be used.
475   *       </LI>
476   *       <LI>
477   *         {@code size-limit} -- An optional integer field whose value
478   *         specifies the maximum number of entries that may be joined with any
479   *         single search
480   *         result entry.
481   *       </LI>
482   *       <LI>
483   *         {@code filter} -- An optional string field whose value is the
484   *         string representation of a filter that will be required to match an
485   *         entry for it to be joined with a search result entry.
486   *       </LI>
487   *       <LI>
488   *         {@code attributes} -- An optional array field whose values are
489   *         strings that are the names of attributes to include in joined
490   *         entries.
491   *       </LI>
492   *       <LI>
493   *         {@code require-match} -- A mandatory Boolean field that indicates
494   *         whether to suppress a search result entry from the set of results
495   *         to the client if it is not joined with any other entries.
496   *       </LI>
497   *       <LI>
498   *         {@code nested-join} -- An optional JSON object field whose value
499   *         represents the join criteria for a nested join operation.  If
500   *         present, the fields in this object are the same as the fields that
501   *         may be used in the top-level {@code value-json} field (optionally
502   *         including another {@code nested-jon} element if desired).
503   *       </LI>
504   *     </UL>
505   *   </LI>
506   * </UL>
507   * <BR><BR>
508   * The following encodings may be used for different types of join rules:
509   * <UL>
510   *   <LI>
511   *     The fields that may be used for a DN join include:
512   *     <UL>
513   *       <LI>
514   *         {@code type} -- A mandatory string field whose value must be
515   *         "{@code dn}".
516   *       </LI>
517   *       <LI>
518   *         {@code source-attribute} -- A mandatory string field whose value is
519   *         the name of the attribute in the source entry that contains the DNs
520   *         of the entries to be joined with that entry.
521   *       </LI>
522   *     </UL>
523   *   </LI>
524   *   <LI>
525   *     The fields that may be used for a reverse DN join include:
526   *     <UL>
527   *       <LI>
528   *         {@code type} -- A mandatory string field whose value must be
529   *         "{@code reverse-dn}".
530   *       </LI>
531   *       <LI>
532   *         {@code target-attribute} -- A mandatory string field whose value is
533   *         the name of the attribute in joined entries that contains a value
534   *         that matches the DN of the source entry.
535   *       </LI>
536   *     </UL>
537   *   </LI>
538   *   <LI>
539   *     The fields that may be used for an equality join include:
540   *     <UL>
541   *       <LI>
542   *         {@code type} -- A mandatory string field whose value must be
543   *         "{@code equality}".
544   *       </LI>
545   *       <LI>
546   *         {@code source-attribute} -- A mandatory string field whose value is
547   *         the name of an attribute in the source entry whose values will be
548   *         used to identify entries to be joined with that source entry.
549   *       </LI>
550   *       <LI>
551   *         {@code target-attribute} -- A mandatory string field whose value is
552   *         the name of the attribute in joined entries that must contain at
553   *         least one of the values from the source attribute in the source
554   *         entry.
555   *       </LI>
556   *       <LI>
557   *         {@code match-all} -- A mandatory Boolean field that indicates
558   *         whether to only join entries in which the target attribute contains
559   *         all of the values of the source attribute.
560   *       </LI>
561   *     </UL>
562   *   </LI>
563   *   <LI>
564   *     The fields that may be used for a contains join include:
565   *     <UL>
566   *       <LI>
567   *         {@code type} -- A mandatory string field whose value must be
568   *         "{@code contains}".
569   *       </LI>
570   *       <LI>
571   *         {@code source-attribute} -- A mandatory string field whose value is
572   *         the name of an attribute in the source entry whose values will be
573   *         used to identify entries to be joined with that source entry.
574   *       </LI>
575   *       <LI>
576   *         {@code target-attribute} -- A mandatory string field whose value is
577   *         the name of the attribute in joined entries that must contain at
578   *         least one value that includes the value of a source attribute as a
579   *         substring.
580   *       </LI>
581   *       <LI>
582   *         {@code match-all} -- A mandatory Boolean field that indicates
583   *         whether to only join entries in which the target attribute contains
584   *         values that contain all of the values of the source attribute as
585   *         substrings.
586   *       </LI>
587   *     </UL>
588   *   </LI>
589   *   <LI>
590   *     The fields that may be used for an AND join include:
591   *     <UL>
592   *       <LI>
593   *         {@code type} -- A mandatory string field whose value must be
594   *         "{@code and}".
595   *       </LI>
596   *       <LI>
597   *         {@code rules} -- A mandatory, non-empty array field whose values
598   *         are the JSON objects that represent the nested join rules that must
599   *         all match an entry for it to be joined with the source entry.
600   *       </LI>
601   *     </UL>
602   *   </LI>
603   *   <LI>
604   *     The fields that may be used for an OR join include:
605   *     <UL>
606   *       <LI>
607   *         {@code type} -- A mandatory string field whose value must be
608   *         "{@code or}".
609   *       </LI>
610   *       <LI>
611   *         {@code rules} -- A mandatory, non-empty array field whose values
612   *         are the JSON objects that represent the nested join rules of which
613   *         at least one must match an entry for it to be joined with the
614   *         source entry.
615   *       </LI>
616   *     </UL>
617   *   </LI>
618   * </UL>
619   *
620   * @return  A JSON object that contains a representation of this control.
621   */
622  @Override()
623  @NotNull()
624  public JSONObject toJSONControl()
625  {
626    return new JSONObject(
627         new JSONField(JSONControlDecodeHelper.JSON_FIELD_OID,
628              JOIN_REQUEST_OID),
629         new JSONField(JSONControlDecodeHelper.JSON_FIELD_CONTROL_NAME,
630              INFO_CONTROL_NAME_JOIN_REQUEST.get()),
631         new JSONField(JSONControlDecodeHelper.JSON_FIELD_CRITICALITY,
632              isCritical()),
633         new JSONField(JSONControlDecodeHelper.JSON_FIELD_VALUE_JSON,
634              encodeValueJSON(joinRequestValue)));
635  }
636
637
638
639  /**
640   * Encodes the provided join request value to a JSON object.
641   *
642   * @param  value  The join request value to encode.  It must not be
643   *                {@code null}.
644   *
645   * @return  The JSON object containing the encoded join request value.
646   */
647  @NotNull()
648  private static JSONObject encodeValueJSON(
649               @NotNull final JoinRequestValue value)
650  {
651    final Map<String,JSONValue> fields = new LinkedHashMap<>();
652    fields.put(JSON_FIELD_JOIN_RULE, value.getJoinRule().toJSON());
653
654
655    final JoinBaseDN joinBaseDN = value.getBaseDN();
656    switch (joinBaseDN.getType())
657    {
658      case JoinBaseDN.BASE_TYPE_SEARCH_BASE:
659        fields.put(JSON_FIELD_BASE_DN_TYPE,
660             new JSONString(JSON_BASE_DN_TYPE_USE_SEARCH_BASE_DN));
661        break;
662
663      case JoinBaseDN.BASE_TYPE_SOURCE_ENTRY_DN:
664        fields.put(JSON_FIELD_BASE_DN_TYPE,
665             new JSONString(JSON_BASE_DN_TYPE_USE_SOURCE_ENTRY_DN));
666        break;
667
668      case JoinBaseDN.BASE_TYPE_CUSTOM:
669        fields.put(JSON_FIELD_BASE_DN_TYPE,
670             new JSONString(JSON_BASE_DN_TYPE_USE_CUSTOM_BASE_DN));
671        fields.put(JSON_FIELD_BASE_DN_VALUE,
672             new JSONString(joinBaseDN.getCustomBaseDN()));
673        break;
674    }
675
676
677    final SearchScope scope = value.getScope();
678    if (scope != null)
679    {
680      switch (scope.intValue())
681      {
682        case SearchScope.BASE_INT_VALUE:
683          fields.put(JSON_FIELD_SCOPE,
684               new JSONString(JSON_SCOPE_BASE_OBJECT));
685          break;
686
687        case SearchScope.ONE_INT_VALUE:
688          fields.put(JSON_FIELD_SCOPE,
689               new JSONString(JSON_SCOPE_SINGLE_LEVEL));
690          break;
691
692        case SearchScope.SUB_INT_VALUE:
693          fields.put(JSON_FIELD_SCOPE,
694               new JSONString(JSON_SCOPE_WHOLE_SUBTREE));
695          break;
696
697        case SearchScope.SUBORDINATE_SUBTREE_INT_VALUE:
698          fields.put(JSON_FIELD_SCOPE,
699               new JSONString(JSON_SCOPE_SUBORDINATE_SUBTREE));
700          break;
701      }
702    }
703
704
705    final DereferencePolicy derefPolicy = value.getDerefPolicy();
706    if (derefPolicy != null)
707    {
708      switch(derefPolicy.intValue())
709      {
710        case 0:
711          fields.put(JSON_FIELD_ALIAS_DEREFERENCING_BEHAVIOR,
712               new JSONString(JSON_ALIAS_BEHAVIOR_NEVER));
713          break;
714        case 1:
715          fields.put(JSON_FIELD_ALIAS_DEREFERENCING_BEHAVIOR,
716               new JSONString(JSON_ALIAS_BEHAVIOR_SEARCHING));
717          break;
718        case 2:
719          fields.put(JSON_FIELD_ALIAS_DEREFERENCING_BEHAVIOR,
720               new JSONString(JSON_ALIAS_BEHAVIOR_FINDING));
721          break;
722        case 3:
723          fields.put(JSON_FIELD_ALIAS_DEREFERENCING_BEHAVIOR,
724               new JSONString(JSON_ALIAS_BEHAVIOR_ALWAYS));
725          break;
726      }
727    }
728
729
730    final Integer sizeLimit = value.getSizeLimit();
731    if (sizeLimit != null)
732    {
733      fields.put(JSON_FIELD_SIZE_LIMIT, new JSONNumber(sizeLimit));
734    }
735
736
737    final Filter filter = value.getFilter();
738    if (filter != null)
739    {
740      fields.put(JSON_FIELD_FILTER, new JSONString(filter.toString()));
741    }
742
743
744    final String[] attributes = value.getAttributes();
745    if ((attributes != null) && (attributes.length > 0))
746    {
747      final List<JSONValue> attrValues = new ArrayList<>(attributes.length);
748      for (final String attr : attributes)
749      {
750        attrValues.add(new JSONString(attr));
751      }
752
753      fields.put(JSON_FIELD_ATTRIBUTES, new JSONArray(attrValues));
754    }
755
756
757    fields.put(JSON_FIELD_REQUIRE_MATCH,
758         new JSONBoolean(value.requireMatch()));
759
760
761    final JoinRequestValue nestedJoin = value.getNestedJoin();
762    if (nestedJoin != null)
763    {
764      fields.put(JSON_FIELD_NESTED_JOIN, encodeValueJSON(nestedJoin));
765    }
766
767    return new JSONObject(fields);
768  }
769
770
771
772  /**
773   * Attempts to decode the provided object as a JSON representation of a join
774   * request control.
775   *
776   * @param  controlObject  The JSON object to be decoded.  It must not be
777   *                        {@code null}.
778   * @param  strict         Indicates whether to use strict mode when decoding
779   *                        the provided JSON object.  If this is {@code true},
780   *                        then this method will throw an exception if the
781   *                        provided JSON object contains any unrecognized
782   *                        fields.  If this is {@code false}, then unrecognized
783   *                        fields will be ignored.
784   *
785   * @return  The join request control that was decoded from the provided JSON
786   *          object.
787   *
788   * @throws  LDAPException  If the provided JSON object cannot be parsed as a
789   *                         valid join request control.
790   */
791  @NotNull()
792  public static JoinRequestControl decodeJSONControl(
793              @NotNull final JSONObject controlObject,
794              final boolean strict)
795         throws LDAPException
796  {
797    final JSONControlDecodeHelper jsonControl = new JSONControlDecodeHelper(
798         controlObject, strict, true, true);
799
800    final ASN1OctetString rawValue = jsonControl.getRawValue();
801    if (rawValue != null)
802    {
803      return new JoinRequestControl(new Control(jsonControl.getOID(),
804           jsonControl.getCriticality(), rawValue));
805    }
806
807
808    final JoinRequestValue joinRequestValue =
809         decodeJoinRequestValueJSON(controlObject, jsonControl.getValueObject(),
810              strict);
811    return new JoinRequestControl(joinRequestValue);
812  }
813
814
815
816  /**
817   * Decodes the provided value object as a join request value.
818   *
819   * @param  controlObject  A JSON object that represents the join request
820   *                        control being decoded.  It must not be {@code null}.
821   * @param  valueObject    A JSON object that represents the join request value
822   *                        being decoded.  It must not be {@code null}.
823   * @param  strict         Indicates whether to use strict mode when decoding
824   *                        the provided JSON object.  If this is {@code true},
825   *                        then this method will throw an exception if the
826   *                        provided JSON object contains any unrecognized
827   *                        fields.  If this is {@code false}, then unrecognized
828   *                        fields will be ignored.
829   *
830   * @return  The join request value that was decoded.
831   *
832   * @throws  LDAPException  If the provided value object does not represent a
833   *                         valid join request control.
834   */
835  @NotNull()
836  private static JoinRequestValue decodeJoinRequestValueJSON(
837               @NotNull final JSONObject controlObject,
838               @NotNull final JSONObject valueObject,
839               final boolean strict)
840          throws LDAPException
841  {
842    final JSONObject joinRuleObject =
843         valueObject.getFieldAsObject(JSON_FIELD_JOIN_RULE);
844    if (joinRuleObject == null)
845    {
846      throw new LDAPException(ResultCode.DECODING_ERROR,
847           ERR_JOIN_REQUEST_JSON_MISSING_FIELD.get(
848                controlObject.toSingleLineString(),
849                JSON_FIELD_JOIN_RULE));
850    }
851
852    final JoinRule joinRule;
853    try
854    {
855      joinRule = JoinRule.decodeJSONJoinRule(joinRuleObject, strict);
856    }
857    catch (final LDAPException e)
858    {
859      Debug.debugException(e);
860      throw new LDAPException(ResultCode.DECODING_ERROR,
861           ERR_JOIN_REQUEST_JSON_INVALID_JOIN_RULE.get(
862                controlObject.toSingleLineString(),
863                JSON_FIELD_JOIN_RULE, e.getMessage()),
864           e);
865    }
866
867
868    final JoinBaseDN baseDN;
869    final String baseDNType =
870         valueObject.getFieldAsString(JSON_FIELD_BASE_DN_TYPE);
871    final String baseDNValue =
872         valueObject.getFieldAsString(JSON_FIELD_BASE_DN_VALUE);
873    if (baseDNType == null)
874    {
875      throw new LDAPException(ResultCode.DECODING_ERROR,
876           ERR_JOIN_REQUEST_JSON_MISSING_FIELD.get(
877                controlObject.toSingleLineString(),
878                JSON_FIELD_BASE_DN_TYPE));
879    }
880
881    switch (baseDNType)
882    {
883      case JSON_BASE_DN_TYPE_USE_SEARCH_BASE_DN:
884        if (baseDNValue != null)
885        {
886          throw new LDAPException(ResultCode.DECODING_ERROR,
887               ERR_JOIN_REQUEST_JSON_DISALLOWED_BASE_DN_VALUE.get(
888                    controlObject.toSingleLineString(),
889                    JSON_FIELD_BASE_DN_VALUE, JSON_FIELD_BASE_DN_TYPE,
890                    baseDNType));
891        }
892
893        baseDN = JoinBaseDN.createUseSearchBaseDN();
894        break;
895
896      case JSON_BASE_DN_TYPE_USE_SOURCE_ENTRY_DN:
897        if (baseDNValue != null)
898        {
899          throw new LDAPException(ResultCode.DECODING_ERROR,
900               ERR_JOIN_REQUEST_JSON_DISALLOWED_BASE_DN_VALUE.get(
901                    controlObject.toSingleLineString(),
902                    JSON_FIELD_BASE_DN_VALUE, JSON_FIELD_BASE_DN_TYPE,
903                    baseDNType));
904        }
905
906        baseDN = JoinBaseDN.createUseSourceEntryDN();
907        break;
908
909      case JSON_BASE_DN_TYPE_USE_CUSTOM_BASE_DN:
910        if (baseDNValue == null)
911        {
912          throw new LDAPException(ResultCode.DECODING_ERROR,
913               ERR_JOIN_REQUEST_JSON_MISSING_BASE_DN_VALUE.get(
914                    controlObject.toSingleLineString(),
915                    JSON_FIELD_BASE_DN_VALUE, JSON_FIELD_BASE_DN_TYPE,
916                    baseDNType));
917        }
918
919        baseDN = JoinBaseDN.createUseCustomBaseDN(baseDNValue);
920        break;
921
922      default:
923        throw new LDAPException(ResultCode.DECODING_ERROR,
924             ERR_JOIN_REQUEST_JSON_INVALID_BASE_DN_TYPE.get(
925                  controlObject.toSingleLineString(), baseDNType,
926                  JSON_FIELD_BASE_DN_TYPE, JSON_BASE_DN_TYPE_USE_SEARCH_BASE_DN,
927                  JSON_BASE_DN_TYPE_USE_SOURCE_ENTRY_DN,
928                  JSON_BASE_DN_TYPE_USE_CUSTOM_BASE_DN));
929    }
930
931
932    final SearchScope scope;
933    final String scopeStr =
934         valueObject.getFieldAsString(JSON_FIELD_SCOPE);
935    if (scopeStr == null)
936    {
937      scope = null;
938    }
939    else
940    {
941      switch (scopeStr)
942      {
943        case JSON_SCOPE_BASE_OBJECT:
944          scope = SearchScope.BASE;
945          break;
946        case JSON_SCOPE_SINGLE_LEVEL:
947          scope = SearchScope.ONE;
948          break;
949        case JSON_SCOPE_WHOLE_SUBTREE:
950          scope = SearchScope.SUB;
951          break;
952        case JSON_SCOPE_SUBORDINATE_SUBTREE:
953          scope = SearchScope.SUBORDINATE_SUBTREE;
954          break;
955        default:
956          throw new LDAPException(ResultCode.DECODING_ERROR,
957               ERR_JOIN_REQUEST_JSON_INVALID_SCOPE.get(
958                    controlObject.toSingleLineString(), scopeStr,
959                    JSON_FIELD_SCOPE, JSON_SCOPE_BASE_OBJECT,
960                    JSON_SCOPE_SINGLE_LEVEL, JSON_SCOPE_WHOLE_SUBTREE,
961                    JSON_SCOPE_SUBORDINATE_SUBTREE));
962      }
963    }
964
965
966    final DereferencePolicy derefPolicy;
967    final String derefStr =
968         valueObject.getFieldAsString(JSON_FIELD_ALIAS_DEREFERENCING_BEHAVIOR);
969    if (derefStr == null)
970    {
971      derefPolicy = null;
972    }
973    else
974    {
975      switch (derefStr)
976      {
977        case JSON_ALIAS_BEHAVIOR_NEVER:
978          derefPolicy = DereferencePolicy.NEVER;
979          break;
980        case JSON_ALIAS_BEHAVIOR_SEARCHING:
981          derefPolicy = DereferencePolicy.SEARCHING;
982          break;
983        case JSON_ALIAS_BEHAVIOR_FINDING:
984          derefPolicy = DereferencePolicy.FINDING;
985          break;
986        case JSON_ALIAS_BEHAVIOR_ALWAYS:
987          derefPolicy = DereferencePolicy.ALWAYS;
988          break;
989        default:
990          throw new LDAPException(ResultCode.DECODING_ERROR,
991               ERR_JOIN_REQUEST_JSON_INVALID_DEREF.get(
992                    controlObject.toSingleLineString(), derefStr,
993                    JSON_FIELD_ALIAS_DEREFERENCING_BEHAVIOR,
994                    JSON_ALIAS_BEHAVIOR_NEVER, JSON_ALIAS_BEHAVIOR_SEARCHING,
995                    JSON_ALIAS_BEHAVIOR_FINDING, JSON_ALIAS_BEHAVIOR_ALWAYS));
996      }
997    }
998
999
1000    final Integer sizeLimit =
1001         valueObject.getFieldAsInteger(JSON_FIELD_SIZE_LIMIT);
1002
1003
1004    final Filter filter;
1005    final String filterStr =  valueObject.getFieldAsString(JSON_FIELD_FILTER);
1006    if (filterStr == null)
1007    {
1008      filter = null;
1009    }
1010    else
1011    {
1012      try
1013      {
1014        filter = Filter.create(filterStr);
1015      }
1016      catch (final Exception e)
1017      {
1018        Debug.debugException(e);
1019        throw new LDAPException(ResultCode.DECODING_ERROR,
1020             ERR_JOIN_REQUEST_JSON_INVALID_FILTER.get(
1021                  controlObject.toSingleLineString(), filterStr,
1022                  JSON_FIELD_FILTER),
1023             e);
1024      }
1025    }
1026
1027
1028    final String[] attributes;
1029    final List<JSONValue> attrValues =
1030         valueObject.getFieldAsArray(JSON_FIELD_ATTRIBUTES);
1031    if (attrValues == null)
1032    {
1033      attributes = null;
1034    }
1035    else
1036    {
1037      attributes = new String[attrValues.size()];
1038      for (int i=0; i < attributes.length; i++)
1039      {
1040        final JSONValue v = attrValues.get(i);
1041        if (v instanceof JSONString)
1042        {
1043          attributes[i] = ((JSONString) v).stringValue();
1044        }
1045        else
1046        {
1047          throw new LDAPException(ResultCode.DECODING_ERROR,
1048               ERR_JOIN_REQUEST_JSON_ATTR_NOT_STRING.get(
1049                    controlObject.toSingleLineString(),
1050                    JSON_FIELD_ATTRIBUTES));
1051        }
1052      }
1053    }
1054
1055
1056    final Boolean requireMatch =
1057         valueObject.getFieldAsBoolean(JSON_FIELD_REQUIRE_MATCH);
1058    if (requireMatch == null)
1059    {
1060      throw new LDAPException(ResultCode.DECODING_ERROR,
1061           ERR_JOIN_REQUEST_JSON_MISSING_FIELD.get(
1062                controlObject.toSingleLineString(),
1063                JSON_FIELD_REQUIRE_MATCH));
1064    }
1065
1066
1067    final JoinRequestValue nestedJoin;
1068    final JSONObject nestedJoinObject =
1069         valueObject.getFieldAsObject(JSON_FIELD_NESTED_JOIN);
1070    if (nestedJoinObject == null)
1071    {
1072      nestedJoin = null;
1073    }
1074    else
1075    {
1076      nestedJoin =
1077           decodeJoinRequestValueJSON(controlObject, nestedJoinObject, strict);
1078    }
1079
1080
1081    if (strict)
1082    {
1083      final List<String> unrecognizedFields =
1084           JSONControlDecodeHelper.getControlObjectUnexpectedFields(
1085                valueObject, JSON_FIELD_JOIN_RULE, JSON_FIELD_BASE_DN_TYPE,
1086                JSON_FIELD_BASE_DN_VALUE, JSON_FIELD_SCOPE,
1087                JSON_FIELD_ALIAS_DEREFERENCING_BEHAVIOR, JSON_FIELD_SIZE_LIMIT,
1088                JSON_FIELD_FILTER, JSON_FIELD_ATTRIBUTES,
1089                JSON_FIELD_REQUIRE_MATCH, JSON_FIELD_NESTED_JOIN);
1090      if (! unrecognizedFields.isEmpty())
1091      {
1092        throw new LDAPException(ResultCode.DECODING_ERROR,
1093             ERR_JOIN_REQUEST_JSON_UNRECOGNIZED_FIELD.get(
1094                  controlObject.toSingleLineString(),
1095                  unrecognizedFields.get(0)));
1096      }
1097    }
1098
1099
1100    return new JoinRequestValue(joinRule, baseDN, scope, derefPolicy, sizeLimit,
1101    filter, attributes, requireMatch, nestedJoin);
1102  }
1103
1104
1105
1106  /**
1107   * {@inheritDoc}
1108   */
1109  @Override()
1110  public void toString(@NotNull final StringBuilder buffer)
1111  {
1112    buffer.append("JoinRequestControl(value=");
1113    joinRequestValue.toString(buffer);
1114    buffer.append(')');
1115  }
1116}