001/* 002 * Copyright 2010-2018 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2010-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.controls; 022 023 024 025import java.util.ArrayList; 026 027import com.unboundid.asn1.ASN1Boolean; 028import com.unboundid.asn1.ASN1Constants; 029import com.unboundid.asn1.ASN1Element; 030import com.unboundid.asn1.ASN1OctetString; 031import com.unboundid.asn1.ASN1Sequence; 032import com.unboundid.ldap.sdk.Control; 033import com.unboundid.ldap.sdk.DecodeableControl; 034import com.unboundid.ldap.sdk.LDAPException; 035import com.unboundid.ldap.sdk.LDAPResult; 036import com.unboundid.ldap.sdk.ResultCode; 037import com.unboundid.util.Debug; 038import com.unboundid.util.NotMutable; 039import com.unboundid.util.StaticUtils; 040import com.unboundid.util.ThreadSafety; 041import com.unboundid.util.ThreadSafetyLevel; 042 043import static com.unboundid.ldap.sdk.controls.ControlMessages.*; 044 045 046 047/** 048 * This class provides an implementation of the LDAP content synchronization 049 * done control as defined in 050 * <a href="http://www.ietf.org/rfc/rfc4533.txt">RFC 4533</a>. Directory 051 * servers may include this control in the search result done message for a 052 * search request containing the content synchronization request control. See 053 * the documentation for the {@link ContentSyncRequestControl} class for more 054 * information about using the content synchronization operation. 055 */ 056@NotMutable() 057@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 058public final class ContentSyncDoneControl 059 extends Control 060 implements DecodeableControl 061{ 062 /** 063 * The OID (1.3.6.1.4.1.4203.1.9.1.3) for the sync done control. 064 */ 065 public static final String SYNC_DONE_OID = "1.3.6.1.4.1.4203.1.9.1.3"; 066 067 068 069 /** 070 * The serial version UID for this serializable class. 071 */ 072 private static final long serialVersionUID = -2723009401737612274L; 073 074 075 076 // The synchronization state cookie. 077 private final ASN1OctetString cookie; 078 079 // Indicates whether to refresh information about deleted entries. 080 private final boolean refreshDeletes; 081 082 083 084 /** 085 * Creates a new empty control instance that is intended to be used only for 086 * decoding controls via the {@code DecodeableControl} interface. 087 */ 088 ContentSyncDoneControl() 089 { 090 cookie = null; 091 refreshDeletes = false; 092 } 093 094 095 096 /** 097 * Creates a new content synchronization done control that provides updated 098 * information about the state of a content synchronization session. 099 * 100 * @param cookie A cookie with an updated synchronization state. It 101 * may be {@code null} if no updated state is 102 * available. 103 * @param refreshDeletes Indicates whether the synchronization processing 104 * has completed a delete phase. 105 */ 106 public ContentSyncDoneControl(final ASN1OctetString cookie, 107 final boolean refreshDeletes) 108 { 109 super(SYNC_DONE_OID, false, encodeValue(cookie, refreshDeletes)); 110 111 this.cookie = cookie; 112 this.refreshDeletes = refreshDeletes; 113 } 114 115 116 117 /** 118 * Creates a new content synchronization done control which is decoded from 119 * the provided information from a generic control. 120 * 121 * @param oid The OID for the control used to create this control. 122 * @param isCritical Indicates whether the control is marked critical. 123 * @param value The encoded value for the control. 124 * 125 * @throws LDAPException If the provided control cannot be decoded as a 126 * content synchronization done control. 127 */ 128 public ContentSyncDoneControl(final String oid, final boolean isCritical, 129 final ASN1OctetString value) 130 throws LDAPException 131 { 132 super(oid, isCritical, value); 133 134 if (value == null) 135 { 136 throw new LDAPException(ResultCode.DECODING_ERROR, 137 ERR_SYNC_DONE_NO_VALUE.get()); 138 } 139 140 ASN1OctetString c = null; 141 Boolean r = null; 142 143 try 144 { 145 final ASN1Sequence s = ASN1Sequence.decodeAsSequence(value.getValue()); 146 for (final ASN1Element e : s.elements()) 147 { 148 switch (e.getType()) 149 { 150 case ASN1Constants.UNIVERSAL_OCTET_STRING_TYPE: 151 if (c == null) 152 { 153 c = ASN1OctetString.decodeAsOctetString(e); 154 } 155 else 156 { 157 throw new LDAPException(ResultCode.DECODING_ERROR, 158 ERR_SYNC_DONE_VALUE_MULTIPLE_COOKIES.get()); 159 } 160 break; 161 162 case ASN1Constants.UNIVERSAL_BOOLEAN_TYPE: 163 if (r == null) 164 { 165 r = ASN1Boolean.decodeAsBoolean(e).booleanValue(); 166 } 167 else 168 { 169 throw new LDAPException(ResultCode.DECODING_ERROR, 170 ERR_SYNC_DONE_VALUE_MULTIPLE_REFRESH_DELETE.get()); 171 } 172 break; 173 174 default: 175 throw new LDAPException(ResultCode.DECODING_ERROR, 176 ERR_SYNC_DONE_VALUE_INVALID_ELEMENT_TYPE.get( 177 StaticUtils.toHex(e.getType()))); 178 } 179 } 180 } 181 catch (final LDAPException le) 182 { 183 throw le; 184 } 185 catch (final Exception e) 186 { 187 Debug.debugException(e); 188 189 throw new LDAPException(ResultCode.DECODING_ERROR, 190 ERR_SYNC_DONE_VALUE_CANNOT_DECODE.get( 191 StaticUtils.getExceptionMessage(e)), e); 192 } 193 194 cookie = c; 195 196 if (r == null) 197 { 198 refreshDeletes = false; 199 } 200 else 201 { 202 refreshDeletes = r; 203 } 204 } 205 206 207 208 /** 209 * Encodes the provided information into a form suitable for use as the value 210 * of this control. 211 * 212 * @param cookie A cookie with an updated synchronization state. It 213 * may be {@code null} if no updated state is 214 * available. 215 * @param refreshDeletes Indicates whether the synchronization processing 216 * has completed a delete phase. 217 * 218 * @return An ASN.1 octet string containing the encoded control value. 219 */ 220 private static ASN1OctetString encodeValue(final ASN1OctetString cookie, 221 final boolean refreshDeletes) 222 { 223 final ArrayList<ASN1Element> elements = new ArrayList<ASN1Element>(2); 224 225 if (cookie != null) 226 { 227 elements.add(cookie); 228 } 229 230 if (refreshDeletes) 231 { 232 elements.add(new ASN1Boolean(refreshDeletes)); 233 } 234 235 return new ASN1OctetString(new ASN1Sequence(elements).encode()); 236 } 237 238 239 240 /** 241 * {@inheritDoc} 242 */ 243 @Override() 244 public ContentSyncDoneControl decodeControl(final String oid, 245 final boolean isCritical, 246 final ASN1OctetString value) 247 throws LDAPException 248 { 249 return new ContentSyncDoneControl(oid, isCritical, value); 250 } 251 252 253 254 /** 255 * Extracts a content synchronization done control from the provided result. 256 * 257 * @param result The result from which to retrieve the content 258 * synchronization done control. 259 * 260 * @return The content synchronization done control contained in the provided 261 * result, or {@code null} if the result did not contain a content 262 * synchronization done control. 263 * 264 * @throws LDAPException If a problem is encountered while attempting to 265 * decode the content synchronization done control 266 * contained in the provided result. 267 */ 268 public static ContentSyncDoneControl get(final LDAPResult result) 269 throws LDAPException 270 { 271 final Control c = 272 result.getResponseControl(SYNC_DONE_OID); 273 if (c == null) 274 { 275 return null; 276 } 277 278 if (c instanceof ContentSyncDoneControl) 279 { 280 return (ContentSyncDoneControl) c; 281 } 282 else 283 { 284 return new ContentSyncDoneControl(c.getOID(), c.isCritical(), 285 c.getValue()); 286 } 287 } 288 289 290 291 /** 292 * Retrieves a cookie providing updated state information for the 293 * synchronization session, if available. 294 * 295 * @return A cookie providing updated state information for the 296 * synchronization session, or {@code null} if none was included in 297 * the control. 298 */ 299 public ASN1OctetString getCookie() 300 { 301 return cookie; 302 } 303 304 305 306 /** 307 * Indicates whether the synchronization processing has completed a delete 308 * phase. 309 * 310 * @return {@code true} if the synchronization processing has completed a 311 * delete phase, or {@code false} if not. 312 */ 313 public boolean refreshDeletes() 314 { 315 return refreshDeletes; 316 } 317 318 319 320 /** 321 * {@inheritDoc} 322 */ 323 @Override() 324 public String getControlName() 325 { 326 return INFO_CONTROL_NAME_CONTENT_SYNC_DONE.get(); 327 } 328 329 330 331 /** 332 * {@inheritDoc} 333 */ 334 @Override() 335 public void toString(final StringBuilder buffer) 336 { 337 buffer.append("ContentSyncDoneControl("); 338 339 if (cookie != null) 340 { 341 buffer.append("cookie='"); 342 StaticUtils.toHex(cookie.getValue(), buffer); 343 buffer.append("', "); 344 } 345 346 buffer.append("refreshDeletes="); 347 buffer.append(refreshDeletes); 348 buffer.append(')'); 349 } 350}