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