001    /*
002     * CDDL HEADER START
003     *
004     * The contents of this file are subject to the terms of the
005     * Common Development and Distribution License, Version 1.0 only
006     * (the "License").  You may not use this file except in compliance
007     * with the License.
008     *
009     * You can obtain a copy of the license at
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2006-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.controls;
028    import org.opends.messages.Message;
029    
030    
031    
032    import java.util.ArrayList;
033    import java.util.Set;
034    
035    import org.opends.server.protocols.asn1.ASN1Boolean;
036    import org.opends.server.protocols.asn1.ASN1Element;
037    import org.opends.server.protocols.asn1.ASN1Integer;
038    import org.opends.server.protocols.asn1.ASN1OctetString;
039    import org.opends.server.protocols.asn1.ASN1Sequence;
040    import org.opends.server.protocols.ldap.LDAPResultCode;
041    import org.opends.server.types.Control;
042    import org.opends.server.types.LDAPException;
043    
044    import static org.opends.server.loggers.debug.DebugLogger.*;
045    import org.opends.server.loggers.debug.DebugTracer;
046    import org.opends.server.types.DebugLogLevel;
047    import static org.opends.messages.ProtocolMessages.*;
048    import static org.opends.server.util.ServerConstants.*;
049    import static org.opends.server.util.StaticUtils.*;
050    
051    
052    
053    /**
054     * This class implements the persistent search control defined in
055     * draft-ietf-ldapext-psearch.  It makes it possible for clients to be notified
056     * of changes to information in the Directory Server as they occur.
057     */
058    public class PersistentSearchControl
059           extends Control
060    {
061      /**
062       * The tracer object for the debug logger.
063       */
064      private static final DebugTracer TRACER = getTracer();
065    
066    
067    
068    
069      // Indicates whether to only return entries that have been updated since the
070      // beginning of the search.
071      private boolean changesOnly;
072    
073      // Indicates whether entries returned as a result of changes to directory data
074      // should include the entry change notification control.
075      private boolean returnECs;
076    
077      // The set of change types associated with this control.
078      private Set<PersistentSearchChangeType> changeTypes;
079    
080    
081    
082      /**
083       * Creates a new persistent search control with the provided information.
084       *
085       * @param  changeTypes  The set of change types for which to provide
086       *                      notification to the client.
087       * @param  changesOnly  Indicates whether to only return changes that match
088       *                      the associated search criteria, or to also return all
089       *                      existing entries that match the filter.
090       * @param  returnECs    Indicates whether to include the entry change
091       *                      notification control in updated entries that match the
092       *                      associated search criteria.
093       */
094      public PersistentSearchControl(Set<PersistentSearchChangeType> changeTypes,
095                                     boolean changesOnly, boolean returnECs)
096      {
097        super(OID_PERSISTENT_SEARCH, true,
098              encodeValue(changeTypes, changesOnly, returnECs));
099    
100    
101        this.changeTypes = changeTypes;
102        this.changesOnly = changesOnly;
103        this.returnECs   = returnECs;
104      }
105    
106    
107    
108      /**
109       * Creates a new persistent search control with the provided information.
110       *
111       * @param  oid          The OID to use for the control.
112       * @param  isCritical   Indicates whether the control should be considered
113       *                      critical for the operation processing.
114       * @param  changeTypes  The set of change types for which to provide
115       *                      notification to the client.
116       * @param  changesOnly  Indicates whether to only return changes that match
117       *                      the associated search criteria, or to also return all
118       *                      existing entries that match the filter.
119       * @param  returnECs    Indicates whether to include the entry change
120       *                      notification control in updated entries that match the
121       *                      associated search criteria.
122       */
123      public PersistentSearchControl(String oid, boolean isCritical,
124                                     Set<PersistentSearchChangeType> changeTypes,
125                                     boolean changesOnly, boolean returnECs)
126      {
127        super(oid, isCritical, encodeValue(changeTypes, changesOnly, returnECs));
128    
129    
130        this.changeTypes = changeTypes;
131        this.changesOnly = changesOnly;
132        this.returnECs   = returnECs;
133      }
134    
135    
136    
137      /**
138       * Creates a new persistent search control with the provided information.
139       *
140       * @param  oid           The OID to use for the control.
141       * @param  isCritical    Indicates whether the control should be considered
142       *                       critical for the operation processing.
143       * @param  changeTypes   The set of change types for which to provide
144       *                       notification to the client.
145       * @param  changesOnly   Indicates whether to only return changes that match
146       *                       the associated search criteria, or to also return all
147       *                       existing entries that match the filter.
148       * @param  returnECs     Indicates whether to include the entry change
149       *                       notification control in updated entries that match
150       *                       the associated search criteria.
151       * @param  encodedValue  The pre-encoded value for the control.
152       */
153      private PersistentSearchControl(String oid, boolean isCritical,
154                                      Set<PersistentSearchChangeType> changeTypes,
155                                      boolean changesOnly, boolean returnECs,
156                                      ASN1OctetString encodedValue)
157      {
158        super(oid, isCritical, encodedValue);
159    
160    
161        this.changeTypes = changeTypes;
162        this.changesOnly = changesOnly;
163        this.returnECs   = returnECs;
164      }
165    
166    
167    
168      /**
169       * Encodes the provided information into an ASN.1 octet string suitable for
170       * use as the control value.
171       *
172       * @param  changeTypes  The set of change types for which to provide
173       *                      notification to the client.
174       * @param  changesOnly  Indicates whether to only return changes that match
175       *                      the associated search criteria, or to also return all
176       *                      existing entries that match the filter.
177       * @param  returnECs    Indicates whether to include the entry change
178       *                      notification control in updated entries that match the
179       *                      associated search criteria.
180       *
181       * @return  An ASN.1 octet string containing the encoded information.
182       */
183      private static ASN1OctetString encodeValue(Set<PersistentSearchChangeType>
184                                                      changeTypes,
185                                                 boolean changesOnly,
186                                                 boolean returnECs)
187      {
188        ArrayList<ASN1Element> elements =
189             new ArrayList<ASN1Element>(3);
190        elements.add(new ASN1Integer(
191             PersistentSearchChangeType.changeTypesToInt(changeTypes)));
192        elements.add(new ASN1Boolean(changesOnly));
193        elements.add(new ASN1Boolean(returnECs));
194    
195    
196        return new ASN1OctetString(new ASN1Sequence(elements).encode());
197      }
198    
199    
200    
201      /**
202       * Creates a new persistent search control from the contents of the provided
203       * control.
204       *
205       * @param  control  The generic control containing the information to use to
206       *                  create this persistent search control.
207       *
208       * @return  The persistent search control decoded from the provided control.
209       *
210       * @throws  LDAPException  If this control cannot be decoded as a valid
211       *                         persistent search control.
212       */
213      public static PersistentSearchControl decodeControl(Control control)
214             throws LDAPException
215      {
216        if (! control.hasValue())
217        {
218          Message message = ERR_PSEARCH_NO_CONTROL_VALUE.get();
219          throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
220        }
221    
222    
223        boolean                         changesOnly;
224        boolean                         returnECs;
225        Set<PersistentSearchChangeType> changeTypes;
226        try
227        {
228          ArrayList<ASN1Element> elements =
229               ASN1Sequence.decodeAsSequence(control.getValue().value()).elements();
230          if (elements.size() != 3)
231          {
232            Message message =
233                ERR_PSEARCH_INVALID_ELEMENT_COUNT.get(elements.size());
234            throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
235          }
236    
237          int changeTypesValue = elements.get(0).decodeAsInteger().intValue();
238          changeTypes = PersistentSearchChangeType.intToTypes(changeTypesValue);
239          changesOnly = elements.get(1).decodeAsBoolean().booleanValue();
240          returnECs   = elements.get(2).decodeAsBoolean().booleanValue();
241        }
242        catch (LDAPException le)
243        {
244          throw le;
245        }
246        catch (Exception e)
247        {
248          if (debugEnabled())
249          {
250            TRACER.debugCaught(DebugLogLevel.ERROR, e);
251          }
252    
253          Message message =
254              ERR_PSEARCH_CANNOT_DECODE_VALUE.get(getExceptionMessage(e));
255          throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message, e);
256        }
257    
258    
259        return new PersistentSearchControl(control.getOID(), control.isCritical(),
260                                           changeTypes, changesOnly, returnECs,
261                                           control.getValue());
262      }
263    
264    
265    
266      /**
267       * Retrieves the set of change types for this persistent search control.
268       *
269       * @return  The set of change types for this persistent search control.
270       */
271      public Set<PersistentSearchChangeType> getChangeTypes()
272      {
273        return changeTypes;
274      }
275    
276    
277    
278      /**
279       * Specifies the set of change types for this persistent search control.
280       *
281       * @param  changeTypes  The set of change types for this persistent search
282       *                      control.
283       */
284      public void setChangeTypes(Set<PersistentSearchChangeType> changeTypes)
285      {
286        this.changeTypes = changeTypes;
287    
288        setValue(encodeValue(changeTypes, changesOnly, returnECs));
289      }
290    
291    
292    
293      /**
294       * Indicates whether to only return changes that match the associated search
295       * criteria, or to also return all existing entries that match the filter.
296       *
297       * @return  <CODE>true</CODE> if only changes to matching entries should be
298       *          returned, or <CODE>false</CODE> if existing matches should also be
299       *          included.
300       */
301      public boolean getChangesOnly()
302      {
303        return changesOnly;
304      }
305    
306    
307    
308      /**
309       * Specifies whether to only return changes that match teh associated search
310       * criteria, or to also return all existing entries that match the filter.
311       *
312       * @param  changesOnly  Indicates whether to only return changes that match
313       *                      the associated search criteria, or to also return all
314       *                      existing entries that match the filter.
315       */
316      public void setChangesOnly(boolean changesOnly)
317      {
318        this.changesOnly = changesOnly;
319    
320        setValue(encodeValue(changeTypes, changesOnly, returnECs));
321      }
322    
323    
324    
325      /**
326       * Indicates whether to include the entry change notification control in
327       * entries returned to the client as the result of a change in the Directory
328       * Server data.
329       *
330       * @return  <CODE>true</CODE> if entry change notification controls should be
331       *          included in applicable entries, or <CODE>false</CODE> if not.
332       */
333      public boolean getReturnECs()
334      {
335        return returnECs;
336      }
337    
338    
339    
340      /**
341       * Specifies whether to include the entry change notification control in
342       * entries returned to the client as a result of a change in the Directory
343       * Server data.
344       *
345       * @param  returnECs  Indicates whether to include the entry change
346       *                    notification control in updated entries that match the
347       *                    associated search criteria.
348       */
349      public void setReturnECs(boolean returnECs)
350      {
351        this.returnECs = returnECs;
352    
353        setValue(encodeValue(changeTypes, changesOnly, returnECs));
354      }
355    
356    
357    
358      /**
359       * Retrieves a string representation of this persistent search control.
360       *
361       * @return  A string representation of this persistent search control.
362       */
363      public String toString()
364      {
365        StringBuilder buffer = new StringBuilder();
366        toString(buffer);
367        return buffer.toString();
368      }
369    
370    
371    
372      /**
373       * Appends a string representation of this persistent search control to the
374       * provided buffer.
375       *
376       * @param  buffer  The buffer to which the information should be appended.
377       */
378      public void toString(StringBuilder buffer)
379      {
380        buffer.append("PersistentSearchControl(changeTypes=\"");
381        PersistentSearchChangeType.changeTypesToString(changeTypes, buffer);
382        buffer.append("\",changesOnly=");
383        buffer.append(changesOnly);
384        buffer.append(",returnECs=");
385        buffer.append(returnECs);
386        buffer.append(")");
387      }
388    }
389