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}