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