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 }