001    /*
002     * Copyright 2010-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.extensions;
022    
023    
024    
025    import java.util.ArrayList;
026    import java.util.Collection;
027    
028    import com.unboundid.asn1.ASN1Element;
029    import com.unboundid.asn1.ASN1OctetString;
030    import com.unboundid.asn1.ASN1Sequence;
031    import com.unboundid.ldap.sdk.Attribute;
032    import com.unboundid.ldap.sdk.ChangeLogEntry;
033    import com.unboundid.ldap.sdk.Control;
034    import com.unboundid.ldap.sdk.Entry;
035    import com.unboundid.ldap.sdk.IntermediateResponse;
036    import com.unboundid.ldap.sdk.LDAPException;
037    import com.unboundid.ldap.sdk.LDAPRuntimeException;
038    import com.unboundid.ldap.sdk.ResultCode;
039    import com.unboundid.ldap.sdk.unboundidds.UnboundIDChangeLogEntry;
040    import com.unboundid.util.Base64;
041    import com.unboundid.util.Debug;
042    import com.unboundid.util.NotMutable;
043    import com.unboundid.util.StaticUtils;
044    import com.unboundid.util.ThreadSafety;
045    import com.unboundid.util.ThreadSafetyLevel;
046    import com.unboundid.util.Validator;
047    
048    import static com.unboundid.ldap.sdk.unboundidds.extensions.ExtOpMessages.*;
049    
050    
051    
052    /**
053     * <BLOCKQUOTE>
054     *   <B>NOTE:</B>  This class is part of the Commercial Edition of the UnboundID
055     *   LDAP SDK for Java.  It is not available for use in applications that
056     *   include only the Standard Edition of the LDAP SDK, and is not supported for
057     *   use in conjunction with non-UnboundID products.
058     * </BLOCKQUOTE>
059     * This class provides an implementation of an intermediate response which
060     * provides information about a changelog entry returned from a Directory
061     * Server.  The changelog entry intermediate response value is encoded as
062     * follows:
063     * <PRE>
064     *   ChangelogEntryIntermediateResponse ::= SEQUENCE {
065     *        resumeToken                  OCTET STRING,
066     *        serverID                     OCTET STRING,
067     *        changelogEntryDN             LDAPDN,
068     *        changelogEntryAttributes     PartialAttributeList,
069     *        ... }
070     * </PRE>
071     */
072    @NotMutable()
073    @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
074    public final class ChangelogEntryIntermediateResponse
075           extends IntermediateResponse
076    {
077      /**
078       * The OID (1.3.6.1.4.1.30221.2.6.11) for the get stream directory values
079       * intermediate response.
080       */
081      public static final String CHANGELOG_ENTRY_INTERMEDIATE_RESPONSE_OID =
082           "1.3.6.1.4.1.30221.2.6.11";
083    
084    
085    
086      /**
087       * The serial version UID for this serializable class.
088       */
089      private static final long serialVersionUID = 5616371094806687752L;
090    
091    
092    
093    
094      // A token that may be used to start retrieving changelog entries
095      // immediately after this entry.
096      private final ASN1OctetString resumeToken;
097    
098      // The changelog entry included in this intermediate response.
099      private final UnboundIDChangeLogEntry changeLogEntry;
100    
101      // The server ID for the server from which the changelog entry was retrieved.
102      private final String serverID;
103    
104    
105    
106      /**
107       * Creates a new changelog entry intermediate response with the provided
108       * information.
109       *
110       * @param  changeLogEntry  The changelog entry included in this intermediate
111       *                         response.  It must not be {@code null}.
112       * @param  serverID        The server ID for the server from which the
113       *                         changelog entry was received.  It must not be
114       *                         {@code null}.
115       * @param  resumeToken     A token that may be used to resume the process of
116       *                         retrieving changes at the point immediately after
117       *                         this change.  It must not be {@code null}.
118       * @param  controls        The set of controls to include in the response.  It
119       *                         may be {@code null} or empty if no controls should
120       *                         be included.
121       */
122      public ChangelogEntryIntermediateResponse(
123                  final ChangeLogEntry changeLogEntry,
124                  final String serverID, final ASN1OctetString resumeToken,
125                  final Control... controls)
126      {
127        super(CHANGELOG_ENTRY_INTERMEDIATE_RESPONSE_OID,
128              encodeValue(changeLogEntry, serverID, resumeToken), controls);
129    
130        if (changeLogEntry instanceof UnboundIDChangeLogEntry)
131        {
132          this.changeLogEntry = (UnboundIDChangeLogEntry) changeLogEntry;
133        }
134        else
135        {
136          try
137          {
138            this.changeLogEntry = new UnboundIDChangeLogEntry(changeLogEntry);
139          }
140          catch (final LDAPException le)
141          {
142            // This should never happen.
143            Debug.debugException(le);
144            throw new LDAPRuntimeException(le);
145          }
146        }
147    
148        this.serverID       = serverID;
149        this.resumeToken    = resumeToken;
150      }
151    
152    
153    
154      /**
155       * Creates a new changelog entry intermediate response from the provided
156       * generic intermediate response.
157       *
158       * @param  r  The generic intermediate response to be decoded.
159       *
160       * @throws  LDAPException  If the provided intermediate response cannot be
161       *                         decoded as a changelog entry response.
162       */
163      public ChangelogEntryIntermediateResponse(final IntermediateResponse r)
164             throws LDAPException
165      {
166        super(r);
167    
168        final ASN1OctetString value = r.getValue();
169        if (value == null)
170        {
171          throw new LDAPException(ResultCode.DECODING_ERROR,
172               ERR_CHANGELOG_ENTRY_IR_NO_VALUE.get());
173        }
174    
175        final ASN1Sequence valueSequence;
176        try
177        {
178          valueSequence = ASN1Sequence.decodeAsSequence(value.getValue());
179        }
180        catch (final Exception e)
181        {
182          Debug.debugException(e);
183          throw new LDAPException(ResultCode.DECODING_ERROR,
184               ERR_CHANGELOG_ENTRY_IR_VALUE_NOT_SEQUENCE.get(
185                    StaticUtils.getExceptionMessage(e)), e);
186        }
187    
188        final ASN1Element[] valueElements = valueSequence.elements();
189        if (valueElements.length != 4)
190        {
191          throw new LDAPException(ResultCode.DECODING_ERROR,
192               ERR_CHANGELOG_ENTRY_IR_INVALID_VALUE_COUNT.get(
193                    valueElements.length));
194        }
195    
196        resumeToken = ASN1OctetString.decodeAsOctetString(valueElements[0]);
197    
198        serverID =
199             ASN1OctetString.decodeAsOctetString(valueElements[1]).stringValue();
200    
201        final String dn =
202             ASN1OctetString.decodeAsOctetString(valueElements[2]).stringValue();
203    
204        try
205        {
206          final ASN1Element[] attrsElements =
207               ASN1Sequence.decodeAsSequence(valueElements[3]).elements();
208          final ArrayList<Attribute> attributes =
209               new ArrayList<Attribute>(attrsElements.length);
210          for (final ASN1Element e : attrsElements)
211          {
212            attributes.add(Attribute.decode(ASN1Sequence.decodeAsSequence(e)));
213          }
214    
215          changeLogEntry = new UnboundIDChangeLogEntry(new Entry(dn, attributes));
216        }
217        catch (final Exception e)
218        {
219          Debug.debugException(e);
220          throw new LDAPException(ResultCode.DECODING_ERROR,
221               ERR_CHANGELOG_ENTRY_IR_ERROR_PARSING_VALUE.get(
222                    StaticUtils.getExceptionMessage(e)), e);
223        }
224      }
225    
226    
227    
228      /**
229       * Encodes the provided information in a form suitable for use as the value of
230       * this intermediate response.
231       *
232       * @param  changeLogEntry  The changelog entry included in this intermediate
233       *                         response.
234       * @param  serverID        The server ID for the server from which the
235       *                         changelog entry was received.
236       * @param  resumeToken     A token that may be used to resume the process of
237       *                         retrieving changes at the point immediately after
238       *                         this change.
239       *
240       * @return  The encoded value.
241       */
242      private static ASN1OctetString encodeValue(
243                                          final ChangeLogEntry changeLogEntry,
244                                          final String serverID,
245                                          final ASN1OctetString resumeToken)
246      {
247        Validator.ensureNotNull(changeLogEntry);
248        Validator.ensureNotNull(serverID);
249        Validator.ensureNotNull(resumeToken);
250    
251        final Collection<Attribute> attrs = changeLogEntry.getAttributes();
252        final ArrayList<ASN1Element> attrElements =
253             new ArrayList<ASN1Element>(attrs.size());
254        for (final Attribute a : attrs)
255        {
256          attrElements.add(a.encode());
257        }
258    
259        final ASN1Sequence s = new ASN1Sequence(
260             resumeToken,
261             new ASN1OctetString(serverID),
262             new ASN1OctetString(changeLogEntry.getDN()),
263             new ASN1Sequence(attrElements));
264    
265        return new ASN1OctetString(s.encode());
266      }
267    
268    
269    
270      /**
271       * Retrieves the changelog entry contained in this intermediate response.
272       *
273       * @return  The changelog entry contained in this intermediate response.
274       */
275      public UnboundIDChangeLogEntry getChangeLogEntry()
276      {
277        return changeLogEntry;
278      }
279    
280    
281    
282      /**
283       * Retrieves the server ID for the server from which the changelog entry was
284       * retrieved.
285       *
286       * @return  The server ID for the server from which the changelog entry was
287       *          retrieved.
288       */
289      public String getServerID()
290      {
291        return serverID;
292      }
293    
294    
295    
296      /**
297       * Retrieves a token that may be used to resume the process of retrieving
298       * changes at the point immediately after this change.
299       *
300       * @return  A token that may be used to resume the process of retrieving
301       *          changes at the point immediately after this change.
302       */
303      public ASN1OctetString getResumeToken()
304      {
305        return resumeToken;
306      }
307    
308    
309    
310      /**
311       * {@inheritDoc}
312       */
313      @Override()
314      public String getIntermediateResponseName()
315      {
316        return INFO_CHANGELOG_ENTRY_IR_NAME.get();
317      }
318    
319    
320    
321      /**
322       * {@inheritDoc}
323       */
324      @Override()
325      public String valueToString()
326      {
327        final StringBuilder buffer = new StringBuilder();
328    
329        buffer.append("changeNumber='");
330        buffer.append(changeLogEntry.getChangeNumber());
331        buffer.append("' changeType='");
332        buffer.append(changeLogEntry.getChangeType().getName());
333        buffer.append("' targetDN='");
334        buffer.append(changeLogEntry.getTargetDN());
335        buffer.append("' serverID='");
336        buffer.append(serverID);
337        buffer.append("' resumeToken='");
338        Base64.encode(resumeToken.getValue(), buffer);
339        buffer.append('\'');
340    
341        return buffer.toString();
342      }
343    
344    
345    
346      /**
347       * {@inheritDoc}
348       */
349      @Override()
350      public void toString(final StringBuilder buffer)
351      {
352        buffer.append("ChangelogEntryIntermediateResponse(");
353    
354        final int messageID = getMessageID();
355        if (messageID >= 0)
356        {
357          buffer.append("messageID=");
358          buffer.append(messageID);
359          buffer.append(", ");
360        }
361    
362        buffer.append("changelogEntry=");
363        changeLogEntry.toString(buffer);
364        buffer.append(", serverID='");
365        buffer.append(serverID);
366        buffer.append("', resumeToken='");
367        Base64.encode(resumeToken.getValue(), buffer);
368        buffer.append('\'');
369    
370        final Control[] controls = getControls();
371        if (controls.length > 0)
372        {
373          buffer.append(", controls={");
374          for (int i=0; i < controls.length; i++)
375          {
376            if (i > 0)
377            {
378              buffer.append(", ");
379            }
380    
381            buffer.append(controls[i]);
382          }
383          buffer.append('}');
384        }
385    
386        buffer.append(')');
387      }
388    }