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 }