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 }