001    /*
002     * Copyright 2008-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2015 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     */
021    package com.unboundid.ldap.sdk.examples;
022    
023    
024    
025    import java.io.IOException;
026    import java.io.OutputStream;
027    import java.io.Serializable;
028    import java.util.LinkedHashMap;
029    import java.util.List;
030    
031    import com.unboundid.ldap.sdk.Control;
032    import com.unboundid.ldap.sdk.LDAPConnection;
033    import com.unboundid.ldap.sdk.LDAPException;
034    import com.unboundid.ldap.sdk.ResultCode;
035    import com.unboundid.ldap.sdk.Version;
036    import com.unboundid.ldif.LDIFChangeRecord;
037    import com.unboundid.ldif.LDIFException;
038    import com.unboundid.ldif.LDIFReader;
039    import com.unboundid.util.LDAPCommandLineTool;
040    import com.unboundid.util.ThreadSafety;
041    import com.unboundid.util.ThreadSafetyLevel;
042    import com.unboundid.util.args.ArgumentException;
043    import com.unboundid.util.args.ArgumentParser;
044    import com.unboundid.util.args.BooleanArgument;
045    import com.unboundid.util.args.ControlArgument;
046    import com.unboundid.util.args.FileArgument;
047    
048    
049    
050    /**
051     * This class provides a simple tool that can be used to perform add, delete,
052     * modify, and modify DN operations against an LDAP directory server.  The
053     * changes to apply can be read either from standard input or from an LDIF file.
054     * <BR><BR>
055     * Some of the APIs demonstrated by this example include:
056     * <UL>
057     *   <LI>Argument Parsing (from the {@code com.unboundid.util.args}
058     *       package)</LI>
059     *   <LI>LDAP Command-Line Tool (from the {@code com.unboundid.util}
060     *       package)</LI>
061     *   <LI>LDIF Processing (from the {@code com.unboundid.ldif} package)</LI>
062     * </UL>
063     * <BR><BR>
064     * The behavior of this utility is controlled by command line arguments.
065     * Supported arguments include those allowed by the {@link LDAPCommandLineTool}
066     * class, as well as the following additional arguments:
067     * <UL>
068     *   <LI>"-f {path}" or "--ldifFile {path}" -- specifies the path to the LDIF
069     *       file containing the changes to apply.  If this is not provided, then
070     *       changes will be read from standard input.</LI>
071     *   <LI>"-a" or "--defaultAdd" -- indicates that any LDIF records encountered
072     *       that do not include a changetype should be treated as add change
073     *       records.  If this is not provided, then such records will be
074     *       rejected.</LI>
075     *   <LI>"-c" or "--continueOnError" -- indicates that processing should
076     *       continue if an error occurs while processing an earlier change.  If
077     *       this is not provided, then the command will exit on the first error
078     *       that occurs.</LI>
079     *   <LI>"--bindControl {control}" -- specifies a control that should be
080     *       included in the bind request sent by this tool before performing any
081     *       update operations.</LI>
082     * </UL>
083     */
084    @ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
085    public final class LDAPModify
086           extends LDAPCommandLineTool
087           implements Serializable
088    {
089      /**
090       * The serial version UID for this serializable class.
091       */
092      private static final long serialVersionUID = -2602159836108416722L;
093    
094    
095    
096      // Indicates whether processing should continue even if an error has occurred.
097      private BooleanArgument continueOnError;
098    
099      // Indicates whether LDIF records without a changetype should be considered
100      // add records.
101      private BooleanArgument defaultAdd;
102    
103      // The argument used to specify any bind controls that should be used.
104      private ControlArgument bindControls;
105    
106      // The LDIF file to be processed.
107      private FileArgument ldifFile;
108    
109    
110    
111      /**
112       * Parse the provided command line arguments and make the appropriate set of
113       * changes.
114       *
115       * @param  args  The command line arguments provided to this program.
116       */
117      public static void main(final String[] args)
118      {
119        final ResultCode resultCode = main(args, System.out, System.err);
120        if (resultCode != ResultCode.SUCCESS)
121        {
122          System.exit(resultCode.intValue());
123        }
124      }
125    
126    
127    
128      /**
129       * Parse the provided command line arguments and make the appropriate set of
130       * changes.
131       *
132       * @param  args       The command line arguments provided to this program.
133       * @param  outStream  The output stream to which standard out should be
134       *                    written.  It may be {@code null} if output should be
135       *                    suppressed.
136       * @param  errStream  The output stream to which standard error should be
137       *                    written.  It may be {@code null} if error messages
138       *                    should be suppressed.
139       *
140       * @return  A result code indicating whether the processing was successful.
141       */
142      public static ResultCode main(final String[] args,
143                                    final OutputStream outStream,
144                                    final OutputStream errStream)
145      {
146        final LDAPModify ldapModify = new LDAPModify(outStream, errStream);
147        return ldapModify.runTool(args);
148      }
149    
150    
151    
152      /**
153       * Creates a new instance of this tool.
154       *
155       * @param  outStream  The output stream to which standard out should be
156       *                    written.  It may be {@code null} if output should be
157       *                    suppressed.
158       * @param  errStream  The output stream to which standard error should be
159       *                    written.  It may be {@code null} if error messages
160       *                    should be suppressed.
161       */
162      public LDAPModify(final OutputStream outStream, final OutputStream errStream)
163      {
164        super(outStream, errStream);
165      }
166    
167    
168    
169      /**
170       * Retrieves the name for this tool.
171       *
172       * @return  The name for this tool.
173       */
174      @Override()
175      public String getToolName()
176      {
177        return "ldapmodify";
178      }
179    
180    
181    
182      /**
183       * Retrieves the description for this tool.
184       *
185       * @return  The description for this tool.
186       */
187      @Override()
188      public String getToolDescription()
189      {
190        return "Perform add, delete, modify, and modify " +
191               "DN operations in an LDAP directory server.";
192      }
193    
194    
195    
196      /**
197       * Retrieves the version string for this tool.
198       *
199       * @return  The version string for this tool.
200       */
201      @Override()
202      public String getToolVersion()
203      {
204        return Version.NUMERIC_VERSION_STRING;
205      }
206    
207    
208    
209      /**
210       * Adds the arguments used by this program that aren't already provided by the
211       * generic {@code LDAPCommandLineTool} framework.
212       *
213       * @param  parser  The argument parser to which the arguments should be added.
214       *
215       * @throws  ArgumentException  If a problem occurs while adding the arguments.
216       */
217      @Override()
218      public void addNonLDAPArguments(final ArgumentParser parser)
219             throws ArgumentException
220      {
221        String description = "Treat LDIF records that do not contain a " +
222                             "changetype as add records.";
223        defaultAdd = new BooleanArgument('a', "defaultAdd", description);
224        parser.addArgument(defaultAdd);
225    
226    
227        description = "Attempt to continue processing additional changes if " +
228                      "an error occurs.";
229        continueOnError = new BooleanArgument('c', "continueOnError",
230                                              description);
231        parser.addArgument(continueOnError);
232    
233    
234        description = "The path to the LDIF file containing the changes.  If " +
235                      "this is not provided, then the changes will be read from " +
236                      "standard input.";
237        ldifFile = new FileArgument('f', "ldifFile", false, 1, "{path}",
238                                    description, true, false, true, false);
239        parser.addArgument(ldifFile);
240    
241    
242        description = "Information about a control to include in the bind request.";
243        bindControls = new ControlArgument(null, "bindControl", false, 0, null,
244             description);
245        parser.addArgument(bindControls);
246      }
247    
248    
249    
250      /**
251       * {@inheritDoc}
252       */
253      @Override()
254      protected List<Control> getBindControls()
255      {
256        return bindControls.getValues();
257      }
258    
259    
260    
261      /**
262       * Performs the actual processing for this tool.  In this case, it gets a
263       * connection to the directory server and uses it to perform the requested
264       * operations.
265       *
266       * @return  The result code for the processing that was performed.
267       */
268      @Override()
269      public ResultCode doToolProcessing()
270      {
271        // Set up the LDIF reader that will be used to read the changes to apply.
272        final LDIFReader ldifReader;
273        try
274        {
275          if (ldifFile.isPresent())
276          {
277            // An LDIF file was specified on the command line, so we will use it.
278            ldifReader = new LDIFReader(ldifFile.getValue());
279          }
280          else
281          {
282            // No LDIF file was specified, so we will read from standard input.
283            ldifReader = new LDIFReader(System.in);
284          }
285        }
286        catch (IOException ioe)
287        {
288          err("I/O error creating the LDIF reader:  ", ioe.getMessage());
289          return ResultCode.LOCAL_ERROR;
290        }
291    
292    
293        // Get the connection to the directory server.
294        final LDAPConnection connection;
295        try
296        {
297          connection = getConnection();
298          out("Connected to ", connection.getConnectedAddress(), ':',
299              connection.getConnectedPort());
300        }
301        catch (LDAPException le)
302        {
303          err("Error connecting to the directory server:  ", le.getMessage());
304          return le.getResultCode();
305        }
306    
307    
308        // Attempt to process and apply the changes to the server.
309        ResultCode resultCode = ResultCode.SUCCESS;
310        while (true)
311        {
312          // Read the next change to process.
313          final LDIFChangeRecord changeRecord;
314          try
315          {
316            changeRecord = ldifReader.readChangeRecord(defaultAdd.isPresent());
317          }
318          catch (LDIFException le)
319          {
320            err("Malformed change record:  ", le.getMessage());
321            if (! le.mayContinueReading())
322            {
323              err("Unable to continue processing the LDIF content.");
324              resultCode = ResultCode.DECODING_ERROR;
325              break;
326            }
327            else if (! continueOnError.isPresent())
328            {
329              resultCode = ResultCode.DECODING_ERROR;
330              break;
331            }
332            else
333            {
334              // We can try to keep processing, so do so.
335              continue;
336            }
337          }
338          catch (IOException ioe)
339          {
340            err("I/O error encountered while reading a change record:  ",
341                ioe.getMessage());
342            resultCode = ResultCode.LOCAL_ERROR;
343            break;
344          }
345    
346    
347          // If the change record was null, then it means there are no more changes
348          // to be processed.
349          if (changeRecord == null)
350          {
351            break;
352          }
353    
354    
355          // Apply the target change to the server.
356          try
357          {
358            out("Processing ", changeRecord.getChangeType().toString(),
359                " operation for ", changeRecord.getDN());
360            changeRecord.processChange(connection);
361            out("Success");
362            out();
363          }
364          catch (LDAPException le)
365          {
366            err("Error:  ", le.getMessage());
367            err("Result Code:  ", le.getResultCode().intValue(), " (",
368                le.getResultCode().getName(), ')');
369            if (le.getMatchedDN() != null)
370            {
371              err("Matched DN:  ", le.getMatchedDN());
372            }
373    
374            if (le.getReferralURLs() != null)
375            {
376              for (final String url : le.getReferralURLs())
377              {
378                err("Referral URL:  ", url);
379              }
380            }
381    
382            err();
383            if (! continueOnError.isPresent())
384            {
385              resultCode = le.getResultCode();
386              break;
387            }
388          }
389        }
390    
391    
392        // Close the connection to the directory server and exit.
393        connection.close();
394        out("Disconnected from the server");
395        return resultCode;
396      }
397    
398    
399    
400      /**
401       * {@inheritDoc}
402       */
403      @Override()
404      public LinkedHashMap<String[],String> getExampleUsages()
405      {
406        final LinkedHashMap<String[],String> examples =
407             new LinkedHashMap<String[],String>();
408    
409        String[] args =
410        {
411          "--hostname", "server.example.com",
412          "--port", "389",
413          "--bindDN", "uid=admin,dc=example,dc=com",
414          "--bindPassword", "password",
415          "--ldifFile", "changes.ldif"
416        };
417        String description =
418             "Attempt to apply the add, delete, modify, and/or modify DN " +
419             "operations contained in the 'changes.ldif' file against the " +
420             "specified directory server.";
421        examples.put(args, description);
422    
423        args = new String[]
424        {
425          "--hostname", "server.example.com",
426          "--port", "389",
427          "--bindDN", "uid=admin,dc=example,dc=com",
428          "--bindPassword", "password",
429          "--continueOnError",
430          "--defaultAdd"
431        };
432        description =
433             "Establish a connection to the specified directory server and then " +
434             "wait for information about the add, delete, modify, and/or modify " +
435             "DN operations to perform to be provided via standard input.  If " +
436             "any invalid operations are requested, then the tool will display " +
437             "an error message but will continue running.  Any LDIF record " +
438             "provided which does not include a 'changeType' line will be " +
439             "treated as an add request.";
440        examples.put(args, description);
441    
442        return examples;
443      }
444    }