001/*
002 * Copyright 2008-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2008-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) 2008-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.ASN1Boolean;
046import com.unboundid.asn1.ASN1Element;
047import com.unboundid.asn1.ASN1OctetString;
048import com.unboundid.asn1.ASN1Sequence;
049import com.unboundid.ldap.sdk.Control;
050import com.unboundid.ldap.sdk.JSONControlDecodeHelper;
051import com.unboundid.ldap.sdk.LDAPException;
052import com.unboundid.ldap.sdk.ResultCode;
053import com.unboundid.util.Debug;
054import com.unboundid.util.NotMutable;
055import com.unboundid.util.NotNull;
056import com.unboundid.util.StaticUtils;
057import com.unboundid.util.ThreadSafety;
058import com.unboundid.util.ThreadSafetyLevel;
059import com.unboundid.util.Validator;
060import com.unboundid.util.json.JSONBoolean;
061import com.unboundid.util.json.JSONField;
062import com.unboundid.util.json.JSONObject;
063import com.unboundid.util.json.JSONString;
064import com.unboundid.util.json.JSONValue;
065
066import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*;
067
068
069
070/**
071 * This class provides a request control which may be used to request that the
072 * associated request be routed to a specific server.  It is primarily intended
073 * for use when the request will pass through a Directory Proxy Server to
074 * indicate that which backend server should be used to process the request.
075 * The server ID for the server to use may be obtained using the
076 * {@link GetServerIDRequestControl}.
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 * If the request is processed successfully, then the result should include a
089 * {@link GetServerIDResponseControl} with the server ID of the server that was
090 * used to process the request.  It may or may not be the same as the server ID
091 * included in the request control, depending on whether an alternate server was
092 * determined to be better suited to handle the request.
093 * <BR><BR>
094 * The criticality for this control may be either {@code true} or {@code false}.
095 * It must have a value with the following encoding:
096 * <PRE>
097 *   RouteToServerRequest ::= SEQUENCE {
098 *        serverID                    [0] OCTET STRING,
099 *        allowAlternateServer        [1] BOOLEAN,
100 *        preferLocalServer           [2] BOOLEAN DEFAULT TRUE,
101 *        preferNonDegradedServer     [3] BOOLEAN DEFAULT TRUE,
102 *        ... }
103 * </PRE>
104 * <BR><BR>
105 * <H2>Example</H2>
106 * The following example demonstrates the process of performing a search to
107 * retrieve an entry using the get server ID request control and then sending a
108 * modify request to that same server using the route to server request control.
109 * <PRE>
110 * // Perform a search to find an entry, and use the get server ID request
111 * // control to figure out which server actually processed the request.
112 * SearchRequest searchRequest = new SearchRequest("dc=example,dc=com",
113 *      SearchScope.BASE, Filter.createPresenceFilter("objectClass"),
114 *      "description");
115 * searchRequest.addControl(new GetServerIDRequestControl());
116 *
117 * SearchResultEntry entry = connection.searchForEntry(searchRequest);
118 * GetServerIDResponseControl serverIDControl =
119 *      GetServerIDResponseControl.get(entry);
120 * String serverID = serverIDControl.getServerID();
121 *
122 * // Send a modify request to update the target entry, and include the route
123 * // to server request control to request that the change be processed on the
124 * // same server that processed the request.
125 * ModifyRequest modifyRequest = new ModifyRequest("dc=example,dc=com",
126 *      new Modification(ModificationType.REPLACE, "description",
127 *           "new description value"));
128 * modifyRequest.addControl(new RouteToServerRequestControl(false, serverID,
129 *      true, true, true));
130 * LDAPResult modifyResult = connection.modify(modifyRequest);
131 * </PRE>
132 */
133@NotMutable()
134@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
135public final class RouteToServerRequestControl
136       extends Control
137{
138  /**
139   * The OID (1.3.6.1.4.1.30221.2.5.16) for the route to server request control.
140   */
141  @NotNull public static final String ROUTE_TO_SERVER_REQUEST_OID =
142       "1.3.6.1.4.1.30221.2.5.16";
143
144
145
146  /**
147   * The BER type for the server ID element.
148   */
149  private static final byte TYPE_SERVER_ID = (byte) 0x80;
150
151
152
153  /**
154   * The BER type for the allow alternate server element.
155   */
156  private static final byte TYPE_ALLOW_ALTERNATE_SERVER = (byte) 0x81;
157
158
159
160  /**
161   * The BER type for the prefer local server element.
162   */
163  private static final byte TYPE_PREFER_LOCAL_SERVER = (byte) 0x82;
164
165
166
167  /**
168   * The BER type for the prefer non-degraded server element.
169   */
170  private static final byte TYPE_PREFER_NON_DEGRADED_SERVER = (byte) 0x83;
171
172
173
174  /**
175   * The name of the field used to hold the allow-alternate-server flag in the
176   * JSON representation of this control.
177   */
178  @NotNull private static final String JSON_FIELD_ALLOW_ALTERNATE_SERVER =
179       "allow-alternate-server";
180
181
182
183  /**
184   * The name of the field used to hold the prefer-local-server flag in the
185   * JSON representation of this control.
186   */
187  @NotNull private static final String JSON_FIELD_PREFER_LOCAL_SERVER =
188       "prefer-local-server";
189
190
191
192  /**
193   * The name of the field used to hold the prefer-non-degraded-server flag in
194   * the JSON representation of this control.
195   */
196  @NotNull private static final String JSON_FIELD_PREFER_NON_DEGRADED_SERVER =
197       "prefer-non-degraded-server";
198
199
200
201  /**
202   * The name of the field used to hold the server ID in the JSON
203   * representation of this control.
204   */
205  @NotNull private static final String JSON_FIELD_SERVER_ID = "server-id";
206
207
208
209  /**
210   * The serial version UID for this serializable class.
211   */
212  private static final long serialVersionUID = 2100638364623466061L;
213
214
215
216  // Indicates whether the associated request may be processed by an alternate
217  // server if the server specified by the given server ID is not suitable for
218  // use.
219  private final boolean allowAlternateServer;
220
221  // Indicates whether the associated request should may be routed to an
222  // alternate server if the target server is more remote than an alternate
223  // server.
224  private final boolean preferLocalServer;
225
226  // Indicates whether the associated request should be routed to an alternate
227  // server if the target server is in a degraded state and an alternate server
228  // is not in a degraded state.
229  private final boolean preferNonDegradedServer;
230
231  // The server ID of the server to which the request should be sent.
232  @NotNull private final String serverID;
233
234
235
236  /**
237   * Creates a new route to server request control with the provided
238   * information.
239   *
240   * @param  isCritical               Indicates whether this control should be
241   *                                  considered critical.
242   * @param  serverID                 The server ID for the server to which the
243   *                                  request should be sent.  It must not be
244   *                                  {@code null}.
245   * @param  allowAlternateServer     Indicates whether the request may be
246   *                                  routed to an alternate server in the
247   *                                  event that the target server is not known,
248   *                                  is not available, or is otherwise unsuited
249   *                                  for use.  If this has a value of
250   *                                  {@code false} and the target server is
251   *                                  unknown or unavailable, then the
252   *                                  associated operation will be rejected.  If
253   *                                  this has a value of {@code true}, then an
254   *                                  intermediate Directory Proxy Server may be
255   *                                  allowed to route the request to a
256   *                                  different server if deemed desirable or
257   *                                  necessary.
258   * @param  preferLocalServer        Indicates whether the associated request
259   *                                  may be routed to an alternate server if
260   *                                  the target server is in a remote location
261   *                                  and a suitable alternate server is
262   *                                  available locally.  This will only be used
263   *                                  if {@code allowAlternateServer} is
264   *                                  {@code true}.
265   * @param  preferNonDegradedServer  Indicates whether the associated request
266   *                                  may be routed to an alternate server if
267   *                                  the target server is in a degraded state
268   *                                  and an alternate server is not in a
269   *                                  degraded state.  This will only be used if
270   *                                  {@code allowAlternateServer} is
271   *                                  {@code true}.
272   */
273  public RouteToServerRequestControl(final boolean isCritical,
274                                     @NotNull final String serverID,
275                                     final boolean allowAlternateServer,
276                                     final boolean preferLocalServer,
277                                     final boolean preferNonDegradedServer)
278  {
279    super(ROUTE_TO_SERVER_REQUEST_OID, isCritical,
280          encodeValue(serverID, allowAlternateServer, preferLocalServer,
281               preferNonDegradedServer));
282
283    this.serverID                = serverID;
284    this.allowAlternateServer    = allowAlternateServer;
285    this.preferLocalServer       = (allowAlternateServer && preferLocalServer);
286    this.preferNonDegradedServer =
287         (allowAlternateServer && preferNonDegradedServer);
288  }
289
290
291
292  /**
293   * Creates a new route to server request control which is decoded from the
294   * provided generic control.
295   *
296   * @param  control  The generic control to be decoded as a route to server
297   *                  request control.
298   *
299   * @throws  LDAPException  If the provided control cannot be decoded as a
300   *                         route to server request control.
301   */
302  public RouteToServerRequestControl(@NotNull final Control control)
303         throws LDAPException
304  {
305    super(control);
306
307    final ASN1OctetString value = control.getValue();
308    if (value == null)
309    {
310      throw new LDAPException(ResultCode.DECODING_ERROR,
311           ERR_ROUTE_TO_SERVER_REQUEST_MISSING_VALUE.get());
312    }
313
314    final ASN1Sequence valueSequence;
315    try
316    {
317      valueSequence = ASN1Sequence.decodeAsSequence(value.getValue());
318    }
319    catch (final Exception e)
320    {
321      Debug.debugException(e);
322      throw new LDAPException(ResultCode.DECODING_ERROR,
323           ERR_ROUTE_TO_SERVER_REQUEST_VALUE_NOT_SEQUENCE.get(
324                StaticUtils.getExceptionMessage(e)), e);
325    }
326
327    try
328    {
329      final ASN1Element[] elements = valueSequence.elements();
330      serverID = ASN1OctetString.decodeAsOctetString(elements[0]).stringValue();
331      allowAlternateServer =
332           ASN1Boolean.decodeAsBoolean(elements[1]).booleanValue();
333
334      boolean preferLocal       = allowAlternateServer;
335      boolean preferNonDegraded = allowAlternateServer;
336      for (int i=2; i < elements.length; i++)
337      {
338        switch (elements[i].getType())
339        {
340          case TYPE_PREFER_LOCAL_SERVER:
341            preferLocal = allowAlternateServer &&
342                 ASN1Boolean.decodeAsBoolean(elements[i]).booleanValue();
343            break;
344          case TYPE_PREFER_NON_DEGRADED_SERVER:
345            preferNonDegraded = allowAlternateServer &&
346                 ASN1Boolean.decodeAsBoolean(elements[i]).booleanValue();
347            break;
348          default:
349            throw new LDAPException(ResultCode.DECODING_ERROR,
350                 ERR_ROUTE_TO_SERVER_REQUEST_INVALID_VALUE_TYPE.get(
351                      StaticUtils.toHex(elements[i].getType())));
352        }
353      }
354
355      preferLocalServer       = preferLocal;
356      preferNonDegradedServer = preferNonDegraded;
357    }
358    catch (final LDAPException le)
359    {
360      Debug.debugException(le);
361      throw le;
362    }
363    catch (final Exception e)
364    {
365      Debug.debugException(e);
366      throw new LDAPException(ResultCode.DECODING_ERROR,
367           ERR_ROUTE_TO_SERVER_REQUEST_ERROR_PARSING_VALUE.get(
368                StaticUtils.getExceptionMessage(e)), e);
369    }
370  }
371
372
373
374  /**
375   * Encodes the provided information into a form suitable for use as the value
376   * of this control.
377   *
378   * @param  serverID                 The server ID for the server to which the
379   *                                  request should be sent.  It must not be
380   *                                  {@code null}.
381   * @param  allowAlternateServer     Indicates whether the request may be
382   *                                  routed to an alternate server in the
383   *                                  event that the target server is not known,
384   *                                  is not available, or is otherwise unsuited
385   *                                  for use.  If this has a value of
386   *                                  {@code false} and the target server is
387   *                                  unknown or unavailable, then the
388   *                                  associated operation will be rejected.  If
389   *                                  this has a value of {@code true}, then an
390   *                                  intermediate Directory Proxy Server may be
391   *                                  allowed to route the request to a
392   *                                  different server if deemed desirable or
393   *                                  necessary.
394   * @param  preferLocalServer        Indicates whether the associated request
395   *                                  may be routed to an alternate server if
396   *                                  the target server is in a remote location
397   *                                  and a suitable alternate server is
398   *                                  available locally.  This will only be used
399   *                                  if {@code allowAlternateServer} is
400   *                                  {@code true}.
401   * @param  preferNonDegradedServer  Indicates whether the associated request
402   *                                  may be routed to an alternate server if
403   *                                  the target server is in a degraded state
404   *                                  and an alternate server is not in a
405   *                                  degraded state.  This will only be used if
406   *                                  {@code allowAlternateServer} is
407   *                                  {@code true}.
408   *
409   * @return  The encoded value for this control.
410   */
411  @NotNull()
412  private static ASN1OctetString encodeValue(@NotNull final String serverID,
413                                      final boolean allowAlternateServer,
414                                      final boolean preferLocalServer,
415                                      final boolean preferNonDegradedServer)
416  {
417    Validator.ensureNotNull(serverID);
418
419    final ArrayList<ASN1Element> elements = new ArrayList<>(4);
420    elements.add(new ASN1OctetString(TYPE_SERVER_ID, serverID));
421    elements.add(
422         new ASN1Boolean(TYPE_ALLOW_ALTERNATE_SERVER, allowAlternateServer));
423
424    if (allowAlternateServer && (! preferLocalServer))
425    {
426      elements.add(new ASN1Boolean(TYPE_PREFER_LOCAL_SERVER, false));
427    }
428
429    if (allowAlternateServer && (! preferNonDegradedServer))
430    {
431      elements.add(new ASN1Boolean(TYPE_PREFER_NON_DEGRADED_SERVER, false));
432    }
433
434    return new ASN1OctetString(new ASN1Sequence(elements).encode());
435  }
436
437
438
439  /**
440   * Retrieves the server ID for the server to which the request should be sent.
441   *
442   * @return  The server ID for the server to which the request should be sent.
443   */
444  @NotNull()
445  public String getServerID()
446  {
447    return serverID;
448  }
449
450
451
452  /**
453   * Indicates whether the request may be routed to an alternate server if the
454   * target server is unknown, unavailable, or otherwise unsuited for use.
455   *
456   * @return  {@code true} if the request may be routed to an alternate server
457   *          if the target server is not suitable for use, or {@code false} if
458   *          the operation should be rejected if it cannot be routed to the
459   *          target server.
460   */
461  public boolean allowAlternateServer()
462  {
463    return allowAlternateServer;
464  }
465
466
467
468  /**
469   * Indicates whether the request may be routed to an alternate server if the
470   * target server is nonlocal and a suitable server is available locally.  This
471   * will only return {@code true} if {@link #allowAlternateServer} also returns
472   * {@code true}.
473   *
474   * @return  {@code true} if the request may be routed to a suitable local
475   *          server if the target server is nonlocal, or {@code false} if the
476   *          nonlocal target server should still be used.
477   */
478  public boolean preferLocalServer()
479  {
480    return preferLocalServer;
481  }
482
483
484
485  /**
486   * Indicates whether the request may be routed to an alternate server if the
487   * target server is in a degraded state and a suitable non-degraded server is
488   * available.  This will only return {@code true} if
489   * {@link #allowAlternateServer} also returns {@code true}.
490   *
491   * @return  {@code true} if the request may be routed to a suitable
492   *          non-degraded server if the target server is degraded, or
493   *          {@code false} if the degraded target server should still be used.
494   */
495  public boolean preferNonDegradedServer()
496  {
497    return preferNonDegradedServer;
498  }
499
500
501
502  /**
503   * {@inheritDoc}
504   */
505  @Override()
506  @NotNull()
507  public String getControlName()
508  {
509    return INFO_CONTROL_NAME_ROUTE_TO_SERVER_REQUEST.get();
510  }
511
512
513
514  /**
515   * Retrieves a representation of this route to server request control as a
516   * JSON object.  The JSON object uses the following fields:
517   * <UL>
518   *   <LI>
519   *     {@code oid} -- A mandatory string field whose value is the object
520   *     identifier for this control.  For the route to server request control,
521   *     the OID is "1.3.6.1.4.1.30221.2.5.16".
522   *   </LI>
523   *   <LI>
524   *     {@code control-name} -- An optional string field whose value is a
525   *     human-readable name for this control.  This field is only intended for
526   *     descriptive purposes, and when decoding a control, the {@code oid}
527   *     field should be used to identify the type of control.
528   *   </LI>
529   *   <LI>
530   *     {@code criticality} -- A mandatory Boolean field used to indicate
531   *     whether this control is considered critical.
532   *   </LI>
533   *   <LI>
534   *     {@code value-base64} -- An optional string field whose value is a
535   *     base64-encoded representation of the raw value for this route to server
536   *     request control.  Exactly one of the {@code value-base64} and
537   *     {@code value-json} fields must be present.
538   *   </LI>
539   *   <LI>
540   *     {@code value-json} -- An optional JSON object field whose value is a
541   *     user-friendly representation of the value for this route to server
542   *     request control.  Exactly one of the {@code value-base64} and
543   *     {@code value-json} fields must be present, and if the
544   *     {@code value-json} field is used, then it will use the following
545   *     fields:
546   *     <UL>
547   *       <LI>
548   *         {@code server-id} -- A mandatory string field whose value is the
549   *         server ID for the server to which the request should be sent.
550   *       </LI>
551   *       <LI>
552   *         {@code allow-alternate-server} -- A mandatory Boolean field that
553   *         indicates whether the Directory Proxy Server may choose to use a
554   *         different server than the one requested if the requested server is
555   *         not known or is not available.
556   *       </LI>
557   *       <LI>
558   *         {@code prefer-local-server} -- An optional Boolean field that
559   *         indicates whether the request may be routed to an alternative
560   *         server if the requested server is not in the same location as the
561   *         Directory Proxy Server.
562   *       </LI>
563   *       <LI>
564   *         {@code prefer-non-degraded-server} -- An optional Boolean field
565   *         that indicates whether the request may be routed to an alternative
566   *         server if the requested server is in a degraded state.
567   *       </LI>
568   *     </UL>
569   *   </LI>
570   * </UL>
571   *
572   * @return  A JSON object that contains a representation of this control.
573   */
574  @Override()
575  @NotNull()
576  public JSONObject toJSONControl()
577  {
578    final Map<String,JSONValue> valueFields = new LinkedHashMap<>();
579    valueFields.put(JSON_FIELD_SERVER_ID, new JSONString(serverID));
580    valueFields.put(JSON_FIELD_ALLOW_ALTERNATE_SERVER,
581         new JSONBoolean(allowAlternateServer));
582
583    if (allowAlternateServer)
584    {
585      valueFields.put(JSON_FIELD_PREFER_LOCAL_SERVER,
586           new JSONBoolean(preferLocalServer));
587      valueFields.put(JSON_FIELD_PREFER_NON_DEGRADED_SERVER,
588           new JSONBoolean(preferNonDegradedServer));
589    }
590
591    return new JSONObject(
592         new JSONField(JSONControlDecodeHelper.JSON_FIELD_OID,
593              ROUTE_TO_SERVER_REQUEST_OID),
594         new JSONField(JSONControlDecodeHelper.JSON_FIELD_CONTROL_NAME,
595              INFO_CONTROL_NAME_ROUTE_TO_SERVER_REQUEST.get()),
596         new JSONField(JSONControlDecodeHelper.JSON_FIELD_CRITICALITY,
597              isCritical()),
598         new JSONField(JSONControlDecodeHelper.JSON_FIELD_VALUE_JSON,
599              new JSONObject(valueFields)));
600  }
601
602
603
604  /**
605   * Attempts to decode the provided object as a JSON representation of a
606   * route to server request control.
607   *
608   * @param  controlObject  The JSON object to be decoded.  It must not be
609   *                        {@code null}.
610   * @param  strict         Indicates whether to use strict mode when decoding
611   *                        the provided JSON object.  If this is {@code true},
612   *                        then this method will throw an exception if the
613   *                        provided JSON object contains any unrecognized
614   *                        fields.  If this is {@code false}, then unrecognized
615   *                        fields will be ignored.
616   *
617   * @return  The route to server request control that was decoded from
618   *          the provided JSON object.
619   *
620   * @throws  LDAPException  If the provided JSON object cannot be parsed as a
621   *                         valid route to server request control.
622   */
623  @NotNull()
624  public static RouteToServerRequestControl decodeJSONControl(
625              @NotNull final JSONObject controlObject,
626              final boolean strict)
627         throws LDAPException
628  {
629    final JSONControlDecodeHelper jsonControl = new JSONControlDecodeHelper(
630         controlObject, strict, true, true);
631
632    final ASN1OctetString rawValue = jsonControl.getRawValue();
633    if (rawValue != null)
634    {
635      return new RouteToServerRequestControl(new Control(
636           jsonControl.getOID(), jsonControl.getCriticality(), rawValue));
637    }
638
639
640    final JSONObject valueObject = jsonControl.getValueObject();
641
642    final String serverID = valueObject.getFieldAsString(JSON_FIELD_SERVER_ID);
643    if (serverID == null)
644    {
645      throw new LDAPException(ResultCode.DECODING_ERROR,
646           ERR_ROUTE_TO_SERVER_REQUEST_JSON_MISSING_FIELD.get(
647                controlObject.toSingleLineString(), JSON_FIELD_SERVER_ID));
648    }
649
650    final Boolean allowAlternateServer =
651         valueObject.getFieldAsBoolean(JSON_FIELD_ALLOW_ALTERNATE_SERVER);
652    if (allowAlternateServer == null)
653    {
654      throw new LDAPException(ResultCode.DECODING_ERROR,
655           ERR_ROUTE_TO_SERVER_REQUEST_JSON_MISSING_FIELD.get(
656                controlObject.toSingleLineString(),
657                JSON_FIELD_ALLOW_ALTERNATE_SERVER));
658    }
659
660    Boolean preferLocalServer =
661         valueObject.getFieldAsBoolean(JSON_FIELD_PREFER_LOCAL_SERVER);
662    if (preferLocalServer == null)
663    {
664      preferLocalServer = true;
665    }
666
667    Boolean preferNonDegradedServer =
668         valueObject.getFieldAsBoolean(JSON_FIELD_PREFER_NON_DEGRADED_SERVER);
669    if (preferNonDegradedServer == null)
670    {
671      preferNonDegradedServer = true;
672    }
673
674
675    if (strict)
676    {
677      final List<String> unrecognizedFields =
678           JSONControlDecodeHelper.getControlObjectUnexpectedFields(
679                valueObject, JSON_FIELD_SERVER_ID,
680                JSON_FIELD_ALLOW_ALTERNATE_SERVER,
681                JSON_FIELD_PREFER_LOCAL_SERVER,
682                JSON_FIELD_PREFER_NON_DEGRADED_SERVER);
683      if (! unrecognizedFields.isEmpty())
684      {
685        throw new LDAPException(ResultCode.DECODING_ERROR,
686             ERR_ROUTE_TO_SERVER_REQUEST_JSON_UNRECOGNIZED_FIELD.get(
687                  controlObject.toSingleLineString(),
688                  unrecognizedFields.get(0)));
689      }
690    }
691
692
693    return new RouteToServerRequestControl(jsonControl.getCriticality(),
694         serverID, allowAlternateServer, preferLocalServer,
695         preferNonDegradedServer);
696  }
697
698
699
700  /**
701   * {@inheritDoc}
702   */
703  @Override()
704  public void toString(@NotNull final StringBuilder buffer)
705  {
706    buffer.append("RouteToServerRequestControl(isCritical=");
707    buffer.append(isCritical());
708    buffer.append(", serverID='");
709    buffer.append(serverID);
710    buffer.append("', allowAlternateServer=");
711    buffer.append(allowAlternateServer);
712    buffer.append(", preferLocalServer=");
713    buffer.append(preferLocalServer);
714    buffer.append(", preferNonDegradedServer=");
715    buffer.append(preferNonDegradedServer);
716    buffer.append(')');
717  }
718}