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