001/* 002 * Copyright 2010-2017 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2010-2017 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 */ 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 public ContentSyncDoneControl decodeControl(final String oid, 244 final boolean isCritical, 245 final ASN1OctetString value) 246 throws LDAPException 247 { 248 return new ContentSyncDoneControl(oid, isCritical, value); 249 } 250 251 252 253 /** 254 * Extracts a content synchronization done control from the provided result. 255 * 256 * @param result The result from which to retrieve the content 257 * synchronization done control. 258 * 259 * @return The content synchronization done control contained in the provided 260 * result, or {@code null} if the result did not contain a content 261 * synchronization done control. 262 * 263 * @throws LDAPException If a problem is encountered while attempting to 264 * decode the content synchronization done control 265 * contained in the provided result. 266 */ 267 public static ContentSyncDoneControl get(final LDAPResult result) 268 throws LDAPException 269 { 270 final Control c = 271 result.getResponseControl(SYNC_DONE_OID); 272 if (c == null) 273 { 274 return null; 275 } 276 277 if (c instanceof ContentSyncDoneControl) 278 { 279 return (ContentSyncDoneControl) c; 280 } 281 else 282 { 283 return new ContentSyncDoneControl(c.getOID(), c.isCritical(), 284 c.getValue()); 285 } 286 } 287 288 289 290 /** 291 * Retrieves a cookie providing updated state information for the 292 * synchronization session, if available. 293 * 294 * @return A cookie providing updated state information for the 295 * synchronization session, or {@code null} if none was included in 296 * the control. 297 */ 298 public ASN1OctetString getCookie() 299 { 300 return cookie; 301 } 302 303 304 305 /** 306 * Indicates whether the synchronization processing has completed a delete 307 * phase. 308 * 309 * @return {@code true} if the synchronization processing has completed a 310 * delete phase, or {@code false} if not. 311 */ 312 public boolean refreshDeletes() 313 { 314 return refreshDeletes; 315 } 316 317 318 319 /** 320 * {@inheritDoc} 321 */ 322 @Override() 323 public String getControlName() 324 { 325 return INFO_CONTROL_NAME_CONTENT_SYNC_DONE.get(); 326 } 327 328 329 330 /** 331 * {@inheritDoc} 332 */ 333 @Override() 334 public void toString(final StringBuilder buffer) 335 { 336 buffer.append("ContentSyncDoneControl("); 337 338 if (cookie != null) 339 { 340 buffer.append("cookie='"); 341 StaticUtils.toHex(cookie.getValue(), buffer); 342 buffer.append("', "); 343 } 344 345 buffer.append("refreshDeletes="); 346 buffer.append(refreshDeletes); 347 buffer.append(')'); 348 } 349}