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}