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}