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    
034    import org.opends.server.protocols.asn1.ASN1Constants;
035    import org.opends.server.protocols.asn1.ASN1Element;
036    import org.opends.server.protocols.asn1.ASN1Enumerated;
037    import org.opends.server.protocols.asn1.ASN1Long;
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.DN;
043    import org.opends.server.types.DebugLogLevel;
044    import org.opends.server.types.LDAPException;
045    
046    import static org.opends.server.loggers.debug.DebugLogger.*;
047    import org.opends.server.loggers.debug.DebugTracer;
048    import static org.opends.messages.ProtocolMessages.*;
049    import static org.opends.server.util.ServerConstants.*;
050    import static org.opends.server.util.StaticUtils.*;
051    
052    
053    
054    /**
055     * This class implements the entry change notification control defined in
056     * draft-ietf-ldapext-psearch.  It may be included in entries returned in
057     * response to a persistent search operation.
058     */
059    public class EntryChangeNotificationControl
060           extends Control
061    {
062      /**
063       * The tracer object for the debug logger.
064       */
065      private static final DebugTracer TRACER = getTracer();
066    
067    
068    
069    
070      // The previous DN for this change notification control.
071      private DN previousDN;
072    
073      // The change number for this change notification control.
074      private long changeNumber;
075    
076      // The change type for this change notification control.
077      private PersistentSearchChangeType changeType;
078    
079    
080    
081      /**
082       * Creates a new entry change notification control with the provided
083       * information.
084       *
085       * @param  changeType    The change type for this change notification control.
086       * @param  changeNumber  The change number for the associated change, or a
087       *                       negative value if no change number is available.
088       */
089      public EntryChangeNotificationControl(PersistentSearchChangeType changeType,
090                                            long changeNumber)
091      {
092        super(OID_ENTRY_CHANGE_NOTIFICATION, false,
093              encodeValue(changeType, null, changeNumber));
094    
095    
096        this.changeType   = changeType;
097        this.changeNumber = changeNumber;
098    
099        previousDN = null;
100      }
101    
102    
103    
104      /**
105       * Creates a new entry change notification control with the provided
106       * information.
107       *
108       * @param  changeType    The change type for this change notification control.
109       * @param  previousDN    The DN that the entry had prior to a modify DN
110       *                       operation, or <CODE>null</CODE> if the operation was
111       *                       not a modify DN.
112       * @param  changeNumber  The change number for the associated change, or a
113       *                       negative value if no change number is available.
114       */
115      public EntryChangeNotificationControl(PersistentSearchChangeType changeType,
116                                            DN previousDN, long changeNumber)
117      {
118        super(OID_ENTRY_CHANGE_NOTIFICATION, false,
119              encodeValue(changeType, previousDN, changeNumber));
120    
121    
122        this.changeType   = changeType;
123        this.previousDN   = previousDN;
124        this.changeNumber = changeNumber;
125      }
126    
127    
128    
129      /**
130       * Creates a new entry change notification control with the provided
131       * information.
132       *
133       * @param  oid           The OID to use for this control.
134       * @param  isCritical    Indicates whether this control should be considered
135       *                       critical to the operation processing.
136       * @param  changeType    The change type for this change notification control.
137       * @param  previousDN    The DN that the entry had prior to a modify DN
138       *                       operation, or <CODE>null</CODE> if the operation was
139       *                       not a modify DN.
140       * @param  changeNumber  The change number for the associated change, or a
141       *                       negative value if no change number is available.
142       */
143      public EntryChangeNotificationControl(String oid, boolean isCritical,
144                                            PersistentSearchChangeType changeType,
145                                            DN previousDN, long changeNumber)
146      {
147        super(oid, isCritical, encodeValue(changeType, previousDN, changeNumber));
148    
149    
150        this.changeType   = changeType;
151        this.previousDN   = previousDN;
152        this.changeNumber = changeNumber;
153      }
154    
155    
156    
157      /**
158       * Creates a new entry change notification control with the provided
159       * information.
160       *
161       * @param  oid           The OID to use for this control.
162       * @param  isCritical    Indicates whether this control should be considered
163       *                       critical to the operation processing.
164       * @param  changeType    The change type for this change notification control.
165       * @param  previousDN    The DN that the entry had prior to a modify DN
166       *                       operation, or <CODE>null</CODE> if the operation was
167       *                       not a modify DN.
168       * @param  changeNumber  The change number for the associated change, or a
169       *                       negative value if no change number is available.
170       * @param  encodedValue  The pre-encoded value for this change notification
171       *                       control.
172       */
173      private EntryChangeNotificationControl(String oid, boolean isCritical,
174                                             PersistentSearchChangeType changeType,
175                                             DN previousDN, long changeNumber,
176                                             ASN1OctetString encodedValue)
177      {
178        super(oid, isCritical, encodedValue);
179    
180    
181        this.changeType   = changeType;
182        this.previousDN   = previousDN;
183        this.changeNumber = changeNumber;
184      }
185    
186    
187    
188      /**
189       * Encodes the provided information into an ASN.1 octet string suitable for
190       * use as the control value.
191       *
192       * @param  changeType    The change type for this change notification control.
193       * @param  previousDN    The DN that the entry had prior to a modify DN
194       *                       operation, or <CODE>null</CODE> if the operation was
195       *                       not a modify DN.
196       * @param  changeNumber  The change number for the associated change, or a
197       *                       negative value if no change number is available.
198       *
199       * @return  An ASN.1 octet string containing the encoded information.
200       */
201      private static ASN1OctetString encodeValue(PersistentSearchChangeType
202                                                      changeType,
203                                                 DN previousDN, long changeNumber)
204      {
205        ArrayList<ASN1Element> elements =
206             new ArrayList<ASN1Element>(3);
207        elements.add(new ASN1Enumerated(changeType.intValue()));
208    
209        if (previousDN != null)
210        {
211          elements.add(new ASN1OctetString(previousDN.toString()));
212        }
213    
214        if (changeNumber > 0)
215        {
216          elements.add(new ASN1Long(changeNumber));
217        }
218    
219    
220        return new ASN1OctetString(new ASN1Sequence(elements).encode());
221      }
222    
223    
224    
225      /**
226       * Creates a new entry change notification control from the contents of the
227       * provided control.
228       *
229       * @param  control  The generic control containing the information to use to
230       *                  create this entry change notification control.
231       *
232       * @return  The entry change notification control decoded from the provided
233       *          control.
234       *
235       * @throws  LDAPException  If this control cannot be decoded as a valid
236       *                         entry change notification control.
237       */
238      public static EntryChangeNotificationControl decodeControl(Control control)
239             throws LDAPException
240      {
241        if (! control.hasValue())
242        {
243          Message message = ERR_ECN_NO_CONTROL_VALUE.get();
244          throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
245        }
246    
247    
248        DN                         previousDN   = null;
249        long                       changeNumber = -1;
250        PersistentSearchChangeType changeType;
251        try
252        {
253          ArrayList<ASN1Element> elements =
254               ASN1Sequence.decodeAsSequence(control.getValue().value()).elements();
255          if ((elements.size() < 1) || (elements.size() > 3))
256          {
257            Message message = ERR_ECN_INVALID_ELEMENT_COUNT.get(elements.size());
258            throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
259          }
260    
261          int changeTypeValue = elements.get(0).decodeAsEnumerated().intValue();
262          changeType = PersistentSearchChangeType.valueOf(changeTypeValue);
263    
264          if (elements.size() == 2)
265          {
266            ASN1Element e = elements.get(1);
267            if (e.getType() == ASN1Constants.UNIVERSAL_OCTET_STRING_TYPE)
268            {
269              if (changeType != PersistentSearchChangeType.MODIFY_DN)
270              {
271                Message message =
272                    ERR_ECN_ILLEGAL_PREVIOUS_DN.get(String.valueOf(changeType));
273                throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
274              }
275    
276              ASN1OctetString rawPreviousDN = e.decodeAsOctetString();
277              previousDN = DN.decode(rawPreviousDN);
278            }
279            else if (e.getType() == ASN1Constants.UNIVERSAL_INTEGER_TYPE)
280            {
281              changeNumber = e.decodeAsLong().longValue();
282            }
283            else
284            {
285              Message message =
286                  ERR_ECN_INVALID_ELEMENT_TYPE.get(byteToHex(e.getType()));
287              throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
288            }
289          }
290          else if (elements.size() == 3)
291          {
292            if (changeType != PersistentSearchChangeType.MODIFY_DN)
293            {
294              Message message =
295                  ERR_ECN_ILLEGAL_PREVIOUS_DN.get(String.valueOf(changeType));
296              throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
297            }
298    
299            ASN1OctetString rawPreviousDN = elements.get(1).decodeAsOctetString();
300            previousDN = DN.decode(rawPreviousDN);
301    
302            changeNumber = elements.get(2).decodeAsLong().longValue();
303          }
304        }
305        catch (LDAPException le)
306        {
307          throw le;
308        }
309        catch (Exception e)
310        {
311          if (debugEnabled())
312          {
313            TRACER.debugCaught(DebugLogLevel.ERROR, e);
314          }
315    
316          Message message = ERR_ECN_CANNOT_DECODE_VALUE.get(getExceptionMessage(e));
317          throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message, e);
318        }
319    
320    
321        return new EntryChangeNotificationControl(control.getOID(),
322                                                  control.isCritical(), changeType,
323                                                  previousDN, changeNumber,
324                                                  control.getValue());
325      }
326    
327    
328    
329      /**
330       * Retrieves the change type for this entry change notification control.
331       *
332       * @return  The change type for this entry change notification control.
333       */
334      public PersistentSearchChangeType getChangeType()
335      {
336        return changeType;
337      }
338    
339    
340    
341      /**
342       * Sets the change type for this entry change notification control.
343       *
344       * @param  changeType  The change type for this entry change notification
345       *                     control.
346       */
347      public void setChangeType(PersistentSearchChangeType changeType)
348      {
349        this.changeType = changeType;
350    
351        setValue(encodeValue(changeType, previousDN, changeNumber));
352      }
353    
354    
355    
356      /**
357       * Retrieves the previous DN for this entry change notification control.
358       *
359       * @return  The previous DN for this entry change notification control, or
360       *          <CODE>null</CODE> if there is none.
361       */
362      public DN getPreviousDN()
363      {
364        return previousDN;
365      }
366    
367    
368    
369      /**
370       * Specifies the previous DN for this entry change notification control.
371       *
372       * @param  previousDN  The previous DN for this entry change notification
373       *                     control.
374       */
375      public void setPreviousDN(DN previousDN)
376      {
377        this.previousDN = previousDN;
378    
379        setValue(encodeValue(changeType, previousDN, changeNumber));
380      }
381    
382    
383    
384      /**
385       * Retrieves the change number for this entry change notification control.
386       *
387       * @return  The change number for this entry change notification control, or a
388       *          negative value if no change number is available.
389       */
390      public long getChangeNumber()
391      {
392        return changeNumber;
393      }
394    
395    
396    
397      /**
398       * Specifies the change number for this entry change notification control.
399       *
400       * @param  changeNumber  The change number for this entry change notification
401       *                       control.
402       */
403      public void setChangeNumber(long changeNumber)
404      {
405        this.changeNumber = changeNumber;
406    
407        setValue(encodeValue(changeType, previousDN, changeNumber));
408      }
409    
410    
411    
412      /**
413       * Retrieves a string representation of this entry change notification
414       * control.
415       *
416       * @return  A string representation of this entry change notification control.
417       */
418      public String toString()
419      {
420        StringBuilder buffer = new StringBuilder();
421        toString(buffer);
422        return buffer.toString();
423      }
424    
425    
426    
427      /**
428       * Appends a string representation of this entry change notification control
429       * to the provided buffer.
430       *
431       * @param  buffer  The buffer to which the information should be appended.
432       */
433      public void toString(StringBuilder buffer)
434      {
435        buffer.append("EntryChangeNotificationControl(changeType=");
436        buffer.append(changeType.toString());
437    
438        if (previousDN != null)
439        {
440          buffer.append(",previousDN=\"");
441          buffer.append(previousDN.toString());
442          buffer.append("\"");
443        }
444    
445        if (changeNumber > 0)
446        {
447          buffer.append(",changeNumber=");
448          buffer.append(changeNumber);
449        }
450    
451        buffer.append(")");
452      }
453    }
454