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}