001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2009 Sun Microsystems, Inc. 025 * Portions copyright 2012-2013 ForgeRock AS. 026 */ 027 028package org.forgerock.opendj.ldif; 029 030import java.io.IOException; 031import java.io.OutputStream; 032import java.io.StringWriter; 033import java.io.Writer; 034import java.util.List; 035 036import org.forgerock.opendj.ldap.Attribute; 037import org.forgerock.opendj.ldap.AttributeDescription; 038import org.forgerock.opendj.ldap.ByteString; 039import org.forgerock.opendj.ldap.DN; 040import org.forgerock.opendj.ldap.Modification; 041import org.forgerock.opendj.ldap.ModificationType; 042import org.forgerock.opendj.ldap.requests.AddRequest; 043import org.forgerock.opendj.ldap.requests.DeleteRequest; 044import org.forgerock.opendj.ldap.requests.ModifyDNRequest; 045import org.forgerock.opendj.ldap.requests.ModifyRequest; 046 047import org.forgerock.util.Reject; 048 049/** 050 * An LDIF change record writer writes change records using the LDAP Data 051 * Interchange Format (LDIF) to a user defined destination. 052 * <p> 053 * The following example reads changes from LDIF, and writes the changes to the 054 * directory server. 055 * 056 * <pre> 057 * InputStream ldif = ...; 058 * LDIFChangeRecordReader reader = new LDIFChangeRecordReader(ldif); 059 * 060 * Connection connection = ...; 061 * connection.bind(...); 062 * 063 * ConnectionChangeRecordWriter writer = 064 * new ConnectionChangeRecordWriter(connection); 065 * while (reader.hasNext()) { 066 * ChangeRecord changeRecord = reader.readChangeRecord(); 067 * writer.writeChangeRecord(changeRecord); 068 * } 069 * </pre> 070 * 071 * @see <a href="http://tools.ietf.org/html/rfc2849">RFC 2849 - The LDAP Data 072 * Interchange Format (LDIF) - Technical Specification </a> 073 */ 074public final class LDIFChangeRecordWriter extends AbstractLDIFWriter implements ChangeRecordWriter { 075 076 /** 077 * Returns the LDIF string representation of the provided change record. 078 * 079 * @param change 080 * The change record. 081 * @return The LDIF string representation of the provided change record. 082 */ 083 public static String toString(final ChangeRecord change) { 084 final StringWriter writer = new StringWriter(128); 085 try { 086 new LDIFChangeRecordWriter(writer).setAddUserFriendlyComments(true).writeChangeRecord( 087 change).close(); 088 } catch (final IOException e) { 089 // Should never happen. 090 throw new IllegalStateException(e); 091 } 092 return writer.toString(); 093 } 094 095 /** 096 * Creates a new LDIF change record writer which will append lines of LDIF 097 * to the provided list. 098 * 099 * @param ldifLines 100 * The list to which lines of LDIF should be appended. 101 */ 102 public LDIFChangeRecordWriter(final List<String> ldifLines) { 103 super(ldifLines); 104 } 105 106 /** 107 * Creates a new LDIF change record writer whose destination is the provided 108 * output stream. 109 * 110 * @param out 111 * The output stream to use. 112 */ 113 public LDIFChangeRecordWriter(final OutputStream out) { 114 super(out); 115 } 116 117 /** 118 * Creates a new LDIF change record writer whose destination is the provided 119 * character stream writer. 120 * 121 * @param writer 122 * The character stream writer to use. 123 */ 124 public LDIFChangeRecordWriter(final Writer writer) { 125 super(writer); 126 } 127 128 /** {@inheritDoc} */ 129 @Override 130 public void close() throws IOException { 131 close0(); 132 } 133 134 /** {@inheritDoc} */ 135 @Override 136 public void flush() throws IOException { 137 flush0(); 138 } 139 140 /** 141 * Specifies whether or not user-friendly comments should be added whenever 142 * distinguished names or UTF-8 attribute values are encountered which 143 * contained non-ASCII characters. The default is {@code false}. 144 * 145 * @param addUserFriendlyComments 146 * {@code true} if user-friendly comments should be added, or 147 * {@code false} otherwise. 148 * @return A reference to this {@code LDIFEntryWriter}. 149 */ 150 public LDIFChangeRecordWriter setAddUserFriendlyComments(final boolean addUserFriendlyComments) { 151 this.addUserFriendlyComments = addUserFriendlyComments; 152 return this; 153 } 154 155 /** 156 * Specifies whether or not all operational attributes should be excluded 157 * from any change records that are written to LDIF. The default is 158 * {@code false}. 159 * 160 * @param excludeOperationalAttributes 161 * {@code true} if all operational attributes should be excluded, 162 * or {@code false} otherwise. 163 * @return A reference to this {@code LDIFChangeRecordWriter}. 164 */ 165 public LDIFChangeRecordWriter setExcludeAllOperationalAttributes( 166 final boolean excludeOperationalAttributes) { 167 this.excludeOperationalAttributes = excludeOperationalAttributes; 168 return this; 169 } 170 171 /** 172 * Specifies whether or not all user attributes should be excluded from any 173 * change records that are written to LDIF. The default is {@code false}. 174 * 175 * @param excludeUserAttributes 176 * {@code true} if all user attributes should be excluded, or 177 * {@code false} otherwise. 178 * @return A reference to this {@code LDIFChangeRecordWriter}. 179 */ 180 public LDIFChangeRecordWriter setExcludeAllUserAttributes(final boolean excludeUserAttributes) { 181 this.excludeUserAttributes = excludeUserAttributes; 182 return this; 183 } 184 185 /** 186 * Excludes the named attribute from any change records that are written to 187 * LDIF. By default all attributes are included unless explicitly excluded. 188 * 189 * @param attributeDescription 190 * The name of the attribute to be excluded. 191 * @return A reference to this {@code LDIFChangeRecordWriter}. 192 */ 193 public LDIFChangeRecordWriter setExcludeAttribute( 194 final AttributeDescription attributeDescription) { 195 Reject.ifNull(attributeDescription); 196 excludeAttributes.add(attributeDescription); 197 return this; 198 } 199 200 /** 201 * Excludes all change records which target entries beneath the named entry 202 * (inclusive) from being written to LDIF. By default all change records are 203 * written unless explicitly excluded or included. 204 * 205 * @param excludeBranch 206 * The distinguished name of the branch to be excluded. 207 * @return A reference to this {@code LDIFChangeRecordWriter}. 208 */ 209 public LDIFChangeRecordWriter setExcludeBranch(final DN excludeBranch) { 210 Reject.ifNull(excludeBranch); 211 excludeBranches.add(excludeBranch); 212 return this; 213 } 214 215 /** 216 * Ensures that the named attribute is not excluded from any change records 217 * that are written to LDIF. By default all attributes are included unless 218 * explicitly excluded. 219 * 220 * @param attributeDescription 221 * The name of the attribute to be included. 222 * @return A reference to this {@code LDIFChangeRecordWriter}. 223 */ 224 public LDIFChangeRecordWriter setIncludeAttribute( 225 final AttributeDescription attributeDescription) { 226 Reject.ifNull(attributeDescription); 227 includeAttributes.add(attributeDescription); 228 return this; 229 } 230 231 /** 232 * Ensures that all change records which target entries beneath the named 233 * entry (inclusive) are written to LDIF. By default all change records are 234 * written unless explicitly excluded or included. 235 * 236 * @param includeBranch 237 * The distinguished name of the branch to be included. 238 * @return A reference to this {@code LDIFChangeRecordWriter}. 239 */ 240 public LDIFChangeRecordWriter setIncludeBranch(final DN includeBranch) { 241 Reject.ifNull(includeBranch); 242 includeBranches.add(includeBranch); 243 return this; 244 } 245 246 /** 247 * Specifies the column at which long lines should be wrapped. A value less 248 * than or equal to zero (the default) indicates that no wrapping should be 249 * performed. 250 * 251 * @param wrapColumn 252 * The column at which long lines should be wrapped. 253 * @return A reference to this {@code LDIFEntryWriter}. 254 */ 255 public LDIFChangeRecordWriter setWrapColumn(final int wrapColumn) { 256 this.wrapColumn = wrapColumn; 257 return this; 258 } 259 260 /** {@inheritDoc} */ 261 @Override 262 public LDIFChangeRecordWriter writeChangeRecord(final AddRequest change) throws IOException { 263 Reject.ifNull(change); 264 265 // Skip if branch containing the entry is excluded. 266 if (isBranchExcluded(change.getName())) { 267 return this; 268 } 269 270 writeKeyAndValue("dn", change.getName().toString()); 271 writeControls(change.getControls()); 272 writeLine("changetype: add"); 273 for (final Attribute attribute : change.getAllAttributes()) { 274 // Filter the attribute if required. 275 if (isAttributeExcluded(attribute.getAttributeDescription())) { 276 continue; 277 } 278 279 final String attributeDescription = attribute.getAttributeDescriptionAsString(); 280 for (final ByteString value : attribute) { 281 writeKeyAndValue(attributeDescription, value); 282 } 283 } 284 285 // Make sure there is a blank line after the entry. 286 impl.println(); 287 288 return this; 289 } 290 291 /** {@inheritDoc} */ 292 @Override 293 public LDIFChangeRecordWriter writeChangeRecord(final ChangeRecord change) throws IOException { 294 Reject.ifNull(change); 295 296 // Skip if branch containing the entry is excluded. 297 if (isBranchExcluded(change.getName())) { 298 return this; 299 } 300 301 final IOException e = change.accept(ChangeRecordVisitorWriter.getInstance(), this); 302 if (e != null) { 303 throw e; 304 } 305 return this; 306 } 307 308 /** {@inheritDoc} */ 309 @Override 310 public LDIFChangeRecordWriter writeChangeRecord(final DeleteRequest change) throws IOException { 311 Reject.ifNull(change); 312 313 // Skip if branch containing the entry is excluded. 314 if (isBranchExcluded(change.getName())) { 315 return this; 316 } 317 318 writeKeyAndValue("dn", change.getName().toString()); 319 writeControls(change.getControls()); 320 writeLine("changetype: delete"); 321 322 // Make sure there is a blank line after the entry. 323 impl.println(); 324 325 return this; 326 } 327 328 /** {@inheritDoc} */ 329 @Override 330 public LDIFChangeRecordWriter writeChangeRecord(final ModifyDNRequest change) 331 throws IOException { 332 Reject.ifNull(change); 333 334 // Skip if branch containing the entry is excluded. 335 if (isBranchExcluded(change.getName())) { 336 return this; 337 } 338 339 writeKeyAndValue("dn", change.getName().toString()); 340 writeControls(change.getControls()); 341 342 /* 343 * Write the changetype. Some older tools may not support the "moddn" 344 * changetype, so only use it if a newSuperior element has been 345 * provided, but use modrdn elsewhere. 346 */ 347 if (change.getNewSuperior() != null) { 348 writeLine("changetype: moddn"); 349 } else { 350 writeLine("changetype: modrdn"); 351 } 352 353 writeKeyAndValue("newrdn", change.getNewRDN().toString()); 354 writeKeyAndValue("deleteoldrdn", change.isDeleteOldRDN() ? "1" : "0"); 355 if (change.getNewSuperior() != null) { 356 writeKeyAndValue("newsuperior", change.getNewSuperior().toString()); 357 } 358 359 // Make sure there is a blank line after the entry. 360 impl.println(); 361 362 return this; 363 } 364 365 /** {@inheritDoc} */ 366 @Override 367 public LDIFChangeRecordWriter writeChangeRecord(final ModifyRequest change) throws IOException { 368 Reject.ifNull(change); 369 370 // If there aren't any modifications, then there's nothing to do. 371 if (change.getModifications().isEmpty()) { 372 return this; 373 } 374 375 // Skip if branch containing the entry is excluded. 376 if (isBranchExcluded(change.getName())) { 377 return this; 378 } 379 380 writeKeyAndValue("dn", change.getName().toString()); 381 writeControls(change.getControls()); 382 writeLine("changetype: modify"); 383 384 for (final Modification modification : change.getModifications()) { 385 final ModificationType type = modification.getModificationType(); 386 final Attribute attribute = modification.getAttribute(); 387 final String attributeDescription = attribute.getAttributeDescriptionAsString(); 388 389 // Filter the attribute if required. 390 if (isAttributeExcluded(attribute.getAttributeDescription())) { 391 continue; 392 } 393 394 writeKeyAndValue(type.toString(), attributeDescription); 395 for (final ByteString value : attribute) { 396 writeKeyAndValue(attributeDescription, value); 397 } 398 writeLine("-"); 399 } 400 401 // Make sure there is a blank line after the entry. 402 impl.println(); 403 404 return this; 405 } 406 407 /** {@inheritDoc} */ 408 @Override 409 public LDIFChangeRecordWriter writeComment(final CharSequence comment) throws IOException { 410 writeComment0(comment); 411 return this; 412 } 413 414}