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 061 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 062 * for proprietary functionality or for external specifications that are not 063 * considered stable or mature enough to be guaranteed to work in an 064 * interoperable way with 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 // A token that may be used to start retrieving changelog entries 099 // immediately after this entry. 100 private final ASN1OctetString resumeToken; 101 102 // The changelog entry included in this intermediate response. 103 private final UnboundIDChangeLogEntry changeLogEntry; 104 105 // The server ID for the server from which the changelog entry was retrieved. 106 private final String serverID; 107 108 109 110 /** 111 * Creates a new changelog entry intermediate response with the provided 112 * information. 113 * 114 * @param changeLogEntry The changelog entry included in this intermediate 115 * response. It must not be {@code null}. 116 * @param serverID The server ID for the server from which the 117 * changelog entry was received. It must not be 118 * {@code null}. 119 * @param resumeToken A token that may be used to resume the process of 120 * retrieving changes at the point immediately after 121 * this change. It must not be {@code null}. 122 * @param controls The set of controls to include in the response. It 123 * may be {@code null} or empty if no controls should 124 * be included. 125 */ 126 public ChangelogEntryIntermediateResponse( 127 final ChangeLogEntry changeLogEntry, 128 final String serverID, final ASN1OctetString resumeToken, 129 final Control... controls) 130 { 131 super(CHANGELOG_ENTRY_INTERMEDIATE_RESPONSE_OID, 132 encodeValue(changeLogEntry, serverID, resumeToken), controls); 133 134 if (changeLogEntry instanceof UnboundIDChangeLogEntry) 135 { 136 this.changeLogEntry = (UnboundIDChangeLogEntry) changeLogEntry; 137 } 138 else 139 { 140 try 141 { 142 this.changeLogEntry = new UnboundIDChangeLogEntry(changeLogEntry); 143 } 144 catch (final LDAPException le) 145 { 146 // This should never happen. 147 Debug.debugException(le); 148 throw new LDAPRuntimeException(le); 149 } 150 } 151 152 this.serverID = serverID; 153 this.resumeToken = resumeToken; 154 } 155 156 157 158 /** 159 * Creates a new changelog entry intermediate response from the provided 160 * generic intermediate response. 161 * 162 * @param r The generic intermediate response to be decoded. 163 * 164 * @throws LDAPException If the provided intermediate response cannot be 165 * decoded as a changelog entry response. 166 */ 167 public ChangelogEntryIntermediateResponse(final IntermediateResponse r) 168 throws LDAPException 169 { 170 super(r); 171 172 final ASN1OctetString value = r.getValue(); 173 if (value == null) 174 { 175 throw new LDAPException(ResultCode.DECODING_ERROR, 176 ERR_CHANGELOG_ENTRY_IR_NO_VALUE.get()); 177 } 178 179 final ASN1Sequence valueSequence; 180 try 181 { 182 valueSequence = ASN1Sequence.decodeAsSequence(value.getValue()); 183 } 184 catch (final Exception e) 185 { 186 Debug.debugException(e); 187 throw new LDAPException(ResultCode.DECODING_ERROR, 188 ERR_CHANGELOG_ENTRY_IR_VALUE_NOT_SEQUENCE.get( 189 StaticUtils.getExceptionMessage(e)), e); 190 } 191 192 final ASN1Element[] valueElements = valueSequence.elements(); 193 if (valueElements.length != 4) 194 { 195 throw new LDAPException(ResultCode.DECODING_ERROR, 196 ERR_CHANGELOG_ENTRY_IR_INVALID_VALUE_COUNT.get( 197 valueElements.length)); 198 } 199 200 resumeToken = ASN1OctetString.decodeAsOctetString(valueElements[0]); 201 202 serverID = 203 ASN1OctetString.decodeAsOctetString(valueElements[1]).stringValue(); 204 205 final String dn = 206 ASN1OctetString.decodeAsOctetString(valueElements[2]).stringValue(); 207 208 try 209 { 210 final ASN1Element[] attrsElements = 211 ASN1Sequence.decodeAsSequence(valueElements[3]).elements(); 212 final ArrayList<Attribute> attributes = 213 new ArrayList<>(attrsElements.length); 214 for (final ASN1Element e : attrsElements) 215 { 216 attributes.add(Attribute.decode(ASN1Sequence.decodeAsSequence(e))); 217 } 218 219 changeLogEntry = new UnboundIDChangeLogEntry(new Entry(dn, attributes)); 220 } 221 catch (final Exception e) 222 { 223 Debug.debugException(e); 224 throw new LDAPException(ResultCode.DECODING_ERROR, 225 ERR_CHANGELOG_ENTRY_IR_ERROR_PARSING_VALUE.get( 226 StaticUtils.getExceptionMessage(e)), e); 227 } 228 } 229 230 231 232 /** 233 * Encodes the provided information in a form suitable for use as the value of 234 * this intermediate response. 235 * 236 * @param changeLogEntry The changelog entry included in this intermediate 237 * response. 238 * @param serverID The server ID for the server from which the 239 * changelog entry was received. 240 * @param resumeToken A token that may be used to resume the process of 241 * retrieving changes at the point immediately after 242 * this change. 243 * 244 * @return The encoded value. 245 */ 246 private static ASN1OctetString encodeValue( 247 final ChangeLogEntry changeLogEntry, 248 final String serverID, 249 final ASN1OctetString resumeToken) 250 { 251 Validator.ensureNotNull(changeLogEntry); 252 Validator.ensureNotNull(serverID); 253 Validator.ensureNotNull(resumeToken); 254 255 final Collection<Attribute> attrs = changeLogEntry.getAttributes(); 256 final ArrayList<ASN1Element> attrElements = 257 new ArrayList<>(attrs.size()); 258 for (final Attribute a : attrs) 259 { 260 attrElements.add(a.encode()); 261 } 262 263 final ASN1Sequence s = new ASN1Sequence( 264 resumeToken, 265 new ASN1OctetString(serverID), 266 new ASN1OctetString(changeLogEntry.getDN()), 267 new ASN1Sequence(attrElements)); 268 269 return new ASN1OctetString(s.encode()); 270 } 271 272 273 274 /** 275 * Retrieves the changelog entry contained in this intermediate response. 276 * 277 * @return The changelog entry contained in this intermediate response. 278 */ 279 public UnboundIDChangeLogEntry getChangeLogEntry() 280 { 281 return changeLogEntry; 282 } 283 284 285 286 /** 287 * Retrieves the server ID for the server from which the changelog entry was 288 * retrieved. 289 * 290 * @return The server ID for the server from which the changelog entry was 291 * retrieved. 292 */ 293 public String getServerID() 294 { 295 return serverID; 296 } 297 298 299 300 /** 301 * Retrieves a token that may be used to resume the process of retrieving 302 * changes at the point immediately after this change. 303 * 304 * @return A token that may be used to resume the process of retrieving 305 * changes at the point immediately after this change. 306 */ 307 public ASN1OctetString getResumeToken() 308 { 309 return resumeToken; 310 } 311 312 313 314 /** 315 * {@inheritDoc} 316 */ 317 @Override() 318 public String getIntermediateResponseName() 319 { 320 return INFO_CHANGELOG_ENTRY_IR_NAME.get(); 321 } 322 323 324 325 /** 326 * {@inheritDoc} 327 */ 328 @Override() 329 public String valueToString() 330 { 331 final StringBuilder buffer = new StringBuilder(); 332 333 buffer.append("changeNumber='"); 334 buffer.append(changeLogEntry.getChangeNumber()); 335 buffer.append("' changeType='"); 336 buffer.append(changeLogEntry.getChangeType().getName()); 337 buffer.append("' targetDN='"); 338 buffer.append(changeLogEntry.getTargetDN()); 339 buffer.append("' serverID='"); 340 buffer.append(serverID); 341 buffer.append("' resumeToken='"); 342 Base64.encode(resumeToken.getValue(), buffer); 343 buffer.append('\''); 344 345 return buffer.toString(); 346 } 347 348 349 350 /** 351 * {@inheritDoc} 352 */ 353 @Override() 354 public void toString(final StringBuilder buffer) 355 { 356 buffer.append("ChangelogEntryIntermediateResponse("); 357 358 final int messageID = getMessageID(); 359 if (messageID >= 0) 360 { 361 buffer.append("messageID="); 362 buffer.append(messageID); 363 buffer.append(", "); 364 } 365 366 buffer.append("changelogEntry="); 367 changeLogEntry.toString(buffer); 368 buffer.append(", serverID='"); 369 buffer.append(serverID); 370 buffer.append("', resumeToken='"); 371 Base64.encode(resumeToken.getValue(), buffer); 372 buffer.append('\''); 373 374 final Control[] controls = getControls(); 375 if (controls.length > 0) 376 { 377 buffer.append(", controls={"); 378 for (int i=0; i < controls.length; i++) 379 { 380 if (i > 0) 381 { 382 buffer.append(", "); 383 } 384 385 buffer.append(controls[i]); 386 } 387 buffer.append('}'); 388 } 389 390 buffer.append(')'); 391 } 392}