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