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