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    }