001/*
002 * Copyright 2008-2022 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2008-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) 2008-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;
041
042import com.unboundid.asn1.ASN1Boolean;
043import com.unboundid.asn1.ASN1Element;
044import com.unboundid.asn1.ASN1OctetString;
045import com.unboundid.asn1.ASN1Sequence;
046import com.unboundid.ldap.sdk.Control;
047import com.unboundid.ldap.sdk.LDAPException;
048import com.unboundid.ldap.sdk.ResultCode;
049import com.unboundid.util.Debug;
050import com.unboundid.util.NotMutable;
051import com.unboundid.util.NotNull;
052import com.unboundid.util.StaticUtils;
053import com.unboundid.util.ThreadSafety;
054import com.unboundid.util.ThreadSafetyLevel;
055import com.unboundid.util.Validator;
056
057import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*;
058
059
060
061/**
062 * This class provides a request control which may be used to request that the
063 * associated request be routed to a specific server.  It is primarily intended
064 * for use when the request will pass through a Directory Proxy Server to
065 * indicate that which backend server should be used to process the request.
066 * The server ID for the server to use may be obtained using the
067 * {@link GetServerIDRequestControl}.
068 * <BR>
069 * <BLOCKQUOTE>
070 *   <B>NOTE:</B>  This class, and other classes within the
071 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
072 *   supported for use against Ping Identity, UnboundID, and
073 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
074 *   for proprietary functionality or for external specifications that are not
075 *   considered stable or mature enough to be guaranteed to work in an
076 *   interoperable way with other types of LDAP servers.
077 * </BLOCKQUOTE>
078 * <BR>
079 * If the request is processed successfully, then the result should include a
080 * {@link GetServerIDResponseControl} with the server ID of the server that was
081 * used to process the request.  It may or may not be the same as the server ID
082 * included in the request control, depending on whether an alternate server was
083 * determined to be better suited to handle the request.
084 * <BR><BR>
085 * The criticality for this control may be either {@code true} or {@code false}.
086 * It must have a value with the following encoding:
087 * <PRE>
088 *   RouteToServerRequest ::= SEQUENCE {
089 *        serverID                    [0] OCTET STRING,
090 *        allowAlternateServer        [1] BOOLEAN,
091 *        preferLocalServer           [2] BOOLEAN DEFAULT TRUE,
092 *        preferNonDegradedServer     [3] BOOLEAN DEFAULT TRUE,
093 *        ... }
094 * </PRE>
095 * <BR><BR>
096 * <H2>Example</H2>
097 * The following example demonstrates the process of performing a search to
098 * retrieve an entry using the get server ID request control and then sending a
099 * modify request to that same server using the route to server request control.
100 * <PRE>
101 * // Perform a search to find an entry, and use the get server ID request
102 * // control to figure out which server actually processed the request.
103 * SearchRequest searchRequest = new SearchRequest("dc=example,dc=com",
104 *      SearchScope.BASE, Filter.createPresenceFilter("objectClass"),
105 *      "description");
106 * searchRequest.addControl(new GetServerIDRequestControl());
107 *
108 * SearchResultEntry entry = connection.searchForEntry(searchRequest);
109 * GetServerIDResponseControl serverIDControl =
110 *      GetServerIDResponseControl.get(entry);
111 * String serverID = serverIDControl.getServerID();
112 *
113 * // Send a modify request to update the target entry, and include the route
114 * // to server request control to request that the change be processed on the
115 * // same server that processed the request.
116 * ModifyRequest modifyRequest = new ModifyRequest("dc=example,dc=com",
117 *      new Modification(ModificationType.REPLACE, "description",
118 *           "new description value"));
119 * modifyRequest.addControl(new RouteToServerRequestControl(false, serverID,
120 *      true, true, true));
121 * LDAPResult modifyResult = connection.modify(modifyRequest);
122 * </PRE>
123 */
124@NotMutable()
125@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
126public final class RouteToServerRequestControl
127       extends Control
128{
129  /**
130   * The OID (1.3.6.1.4.1.30221.2.5.16) for the route to server request control.
131   */
132  @NotNull public static final String ROUTE_TO_SERVER_REQUEST_OID =
133       "1.3.6.1.4.1.30221.2.5.16";
134
135
136
137  /**
138   * The BER type for the server ID element.
139   */
140  private static final byte TYPE_SERVER_ID = (byte) 0x80;
141
142
143
144  /**
145   * The BER type for the allow alternate server element.
146   */
147  private static final byte TYPE_ALLOW_ALTERNATE_SERVER = (byte) 0x81;
148
149
150
151  /**
152   * The BER type for the prefer local server element.
153   */
154  private static final byte TYPE_PREFER_LOCAL_SERVER = (byte) 0x82;
155
156
157
158  /**
159   * The BER type for the prefer non-degraded server element.
160   */
161  private static final byte TYPE_PREFER_NON_DEGRADED_SERVER = (byte) 0x83;
162
163
164
165  /**
166   * The serial version UID for this serializable class.
167   */
168  private static final long serialVersionUID = 2100638364623466061L;
169
170
171
172  // Indicates whether the associated request may be processed by an alternate
173  // server if the server specified by the given server ID is not suitable for
174  // use.
175  private final boolean allowAlternateServer;
176
177  // Indicates whether the associated request should may be routed to an
178  // alternate server if the target server is more remote than an alternate
179  // server.
180  private final boolean preferLocalServer;
181
182  // Indicates whether the associated request should be routed to an alternate
183  // server if the target server is in a degraded state and an alternate server
184  // is not in a degraded state.
185  private final boolean preferNonDegradedServer;
186
187  // The server ID of the server to which the request should be sent.
188  @NotNull private final String serverID;
189
190
191
192  /**
193   * Creates a new route to server request control with the provided
194   * information.
195   *
196   * @param  isCritical               Indicates whether this control should be
197   *                                  considered critical.
198   * @param  serverID                 The server ID for the server to which the
199   *                                  request should be sent.  It must not be
200   *                                  {@code null}.
201   * @param  allowAlternateServer     Indicates whether the request may be
202   *                                  routed to an alternate server in the
203   *                                  event that the target server is not known,
204   *                                  is not available, or is otherwise unsuited
205   *                                  for use.  If this has a value of
206   *                                  {@code false} and the target server is
207   *                                  unknown or unavailable, then the
208   *                                  associated operation will be rejected.  If
209   *                                  this has a value of {@code true}, then an
210   *                                  intermediate Directory Proxy Server may be
211   *                                  allowed to route the request to a
212   *                                  different server if deemed desirable or
213   *                                  necessary.
214   * @param  preferLocalServer        Indicates whether the associated request
215   *                                  may be routed to an alternate server if
216   *                                  the target server is in a remote location
217   *                                  and a suitable alternate server is
218   *                                  available locally.  This will only be used
219   *                                  if {@code allowAlternateServer} is
220   *                                  {@code true}.
221   * @param  preferNonDegradedServer  Indicates whether the associated request
222   *                                  may be routed to an alternate server if
223   *                                  the target server is in a degraded state
224   *                                  and an alternate server is not in a
225   *                                  degraded state.  This will only be used if
226   *                                  {@code allowAlternateServer} is
227   *                                  {@code true}.
228   */
229  public RouteToServerRequestControl(final boolean isCritical,
230                                     @NotNull final String serverID,
231                                     final boolean allowAlternateServer,
232                                     final boolean preferLocalServer,
233                                     final boolean preferNonDegradedServer)
234  {
235    super(ROUTE_TO_SERVER_REQUEST_OID, isCritical,
236          encodeValue(serverID, allowAlternateServer, preferLocalServer,
237               preferNonDegradedServer));
238
239    this.serverID                = serverID;
240    this.allowAlternateServer    = allowAlternateServer;
241    this.preferLocalServer       = (allowAlternateServer && preferLocalServer);
242    this.preferNonDegradedServer =
243         (allowAlternateServer && preferNonDegradedServer);
244  }
245
246
247
248  /**
249   * Creates a new route to server request control which is decoded from the
250   * provided generic control.
251   *
252   * @param  control  The generic control to be decoded as a route to server
253   *                  request control.
254   *
255   * @throws  LDAPException  If the provided control cannot be decoded as a
256   *                         route to server request control.
257   */
258  public RouteToServerRequestControl(@NotNull final Control control)
259         throws LDAPException
260  {
261    super(control);
262
263    final ASN1OctetString value = control.getValue();
264    if (value == null)
265    {
266      throw new LDAPException(ResultCode.DECODING_ERROR,
267           ERR_ROUTE_TO_SERVER_REQUEST_MISSING_VALUE.get());
268    }
269
270    final ASN1Sequence valueSequence;
271    try
272    {
273      valueSequence = ASN1Sequence.decodeAsSequence(value.getValue());
274    }
275    catch (final Exception e)
276    {
277      Debug.debugException(e);
278      throw new LDAPException(ResultCode.DECODING_ERROR,
279           ERR_ROUTE_TO_SERVER_REQUEST_VALUE_NOT_SEQUENCE.get(
280                StaticUtils.getExceptionMessage(e)), e);
281    }
282
283    try
284    {
285      final ASN1Element[] elements = valueSequence.elements();
286      serverID = ASN1OctetString.decodeAsOctetString(elements[0]).stringValue();
287      allowAlternateServer =
288           ASN1Boolean.decodeAsBoolean(elements[1]).booleanValue();
289
290      boolean preferLocal       = allowAlternateServer;
291      boolean preferNonDegraded = allowAlternateServer;
292      for (int i=2; i < elements.length; i++)
293      {
294        switch (elements[i].getType())
295        {
296          case TYPE_PREFER_LOCAL_SERVER:
297            preferLocal = allowAlternateServer &&
298                 ASN1Boolean.decodeAsBoolean(elements[i]).booleanValue();
299            break;
300          case TYPE_PREFER_NON_DEGRADED_SERVER:
301            preferNonDegraded = allowAlternateServer &&
302                 ASN1Boolean.decodeAsBoolean(elements[i]).booleanValue();
303            break;
304          default:
305            throw new LDAPException(ResultCode.DECODING_ERROR,
306                 ERR_ROUTE_TO_SERVER_REQUEST_INVALID_VALUE_TYPE.get(
307                      StaticUtils.toHex(elements[i].getType())));
308        }
309      }
310
311      preferLocalServer       = preferLocal;
312      preferNonDegradedServer = preferNonDegraded;
313    }
314    catch (final LDAPException le)
315    {
316      Debug.debugException(le);
317      throw le;
318    }
319    catch (final Exception e)
320    {
321      Debug.debugException(e);
322      throw new LDAPException(ResultCode.DECODING_ERROR,
323           ERR_ROUTE_TO_SERVER_REQUEST_ERROR_PARSING_VALUE.get(
324                StaticUtils.getExceptionMessage(e)), e);
325    }
326  }
327
328
329
330  /**
331   * Encodes the provided information into a form suitable for use as the value
332   * of this control.
333   *
334   * @param  serverID                 The server ID for the server to which the
335   *                                  request should be sent.  It must not be
336   *                                  {@code null}.
337   * @param  allowAlternateServer     Indicates whether the request may be
338   *                                  routed to an alternate server in the
339   *                                  event that the target server is not known,
340   *                                  is not available, or is otherwise unsuited
341   *                                  for use.  If this has a value of
342   *                                  {@code false} and the target server is
343   *                                  unknown or unavailable, then the
344   *                                  associated operation will be rejected.  If
345   *                                  this has a value of {@code true}, then an
346   *                                  intermediate Directory Proxy Server may be
347   *                                  allowed to route the request to a
348   *                                  different server if deemed desirable or
349   *                                  necessary.
350   * @param  preferLocalServer        Indicates whether the associated request
351   *                                  may be routed to an alternate server if
352   *                                  the target server is in a remote location
353   *                                  and a suitable alternate server is
354   *                                  available locally.  This will only be used
355   *                                  if {@code allowAlternateServer} is
356   *                                  {@code true}.
357   * @param  preferNonDegradedServer  Indicates whether the associated request
358   *                                  may be routed to an alternate server if
359   *                                  the target server is in a degraded state
360   *                                  and an alternate server is not in a
361   *                                  degraded state.  This will only be used if
362   *                                  {@code allowAlternateServer} is
363   *                                  {@code true}.
364   *
365   * @return  The encoded value for this control.
366   */
367  @NotNull()
368  private static ASN1OctetString encodeValue(@NotNull final String serverID,
369                                      final boolean allowAlternateServer,
370                                      final boolean preferLocalServer,
371                                      final boolean preferNonDegradedServer)
372  {
373    Validator.ensureNotNull(serverID);
374
375    final ArrayList<ASN1Element> elements = new ArrayList<>(4);
376    elements.add(new ASN1OctetString(TYPE_SERVER_ID, serverID));
377    elements.add(
378         new ASN1Boolean(TYPE_ALLOW_ALTERNATE_SERVER, allowAlternateServer));
379
380    if (allowAlternateServer && (! preferLocalServer))
381    {
382      elements.add(new ASN1Boolean(TYPE_PREFER_LOCAL_SERVER, false));
383    }
384
385    if (allowAlternateServer && (! preferNonDegradedServer))
386    {
387      elements.add(new ASN1Boolean(TYPE_PREFER_NON_DEGRADED_SERVER, false));
388    }
389
390    return new ASN1OctetString(new ASN1Sequence(elements).encode());
391  }
392
393
394
395  /**
396   * Retrieves the server ID for the server to which the request should be sent.
397   *
398   * @return  The server ID for the server to which the request should be sent.
399   */
400  @NotNull()
401  public String getServerID()
402  {
403    return serverID;
404  }
405
406
407
408  /**
409   * Indicates whether the request may be routed to an alternate server if the
410   * target server is unknown, unavailable, or otherwise unsuited for use.
411   *
412   * @return  {@code true} if the request may be routed to an alternate server
413   *          if the target server is not suitable for use, or {@code false} if
414   *          the operation should be rejected if it cannot be routed to the
415   *          target server.
416   */
417  public boolean allowAlternateServer()
418  {
419    return allowAlternateServer;
420  }
421
422
423
424  /**
425   * Indicates whether the request may be routed to an alternate server if the
426   * target server is nonlocal and a suitable server is available locally.  This
427   * will only return {@code true} if {@link #allowAlternateServer} also returns
428   * {@code true}.
429   *
430   * @return  {@code true} if the request may be routed to a suitable local
431   *          server if the target server is nonlocal, or {@code false} if the
432   *          nonlocal target server should still be used.
433   */
434  public boolean preferLocalServer()
435  {
436    return preferLocalServer;
437  }
438
439
440
441  /**
442   * Indicates whether the request may be routed to an alternate server if the
443   * target server is in a degraded state and a suitable non-degraded server is
444   * available.  This will only return {@code true} if
445   * {@link #allowAlternateServer} also returns {@code true}.
446   *
447   * @return  {@code true} if the request may be routed to a suitable
448   *          non-degraded server if the target server is degraded, or
449   *          {@code false} if the degraded target server should still be used.
450   */
451  public boolean preferNonDegradedServer()
452  {
453    return preferNonDegradedServer;
454  }
455
456
457
458  /**
459   * {@inheritDoc}
460   */
461  @Override()
462  @NotNull()
463  public String getControlName()
464  {
465    return INFO_CONTROL_NAME_ROUTE_TO_SERVER_REQUEST.get();
466  }
467
468
469
470  /**
471   * {@inheritDoc}
472   */
473  @Override()
474  public void toString(@NotNull final StringBuilder buffer)
475  {
476    buffer.append("RouteToServerRequestControl(isCritical=");
477    buffer.append(isCritical());
478    buffer.append(", serverID='");
479    buffer.append(serverID);
480    buffer.append("', allowAlternateServer=");
481    buffer.append(allowAlternateServer);
482    buffer.append(", preferLocalServer=");
483    buffer.append(preferLocalServer);
484    buffer.append(", preferNonDegradedServer=");
485    buffer.append(preferNonDegradedServer);
486    buffer.append(')');
487  }
488}