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}