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 */
021package com.unboundid.ldap.sdk.examples;
022
023
024
025import java.io.OutputStream;
026import java.io.Serializable;
027import java.text.ParseException;
028import java.util.LinkedHashMap;
029import java.util.List;
030
031import com.unboundid.ldap.sdk.CompareRequest;
032import com.unboundid.ldap.sdk.CompareResult;
033import com.unboundid.ldap.sdk.Control;
034import com.unboundid.ldap.sdk.LDAPConnection;
035import com.unboundid.ldap.sdk.LDAPException;
036import com.unboundid.ldap.sdk.ResultCode;
037import com.unboundid.ldap.sdk.Version;
038import com.unboundid.util.Base64;
039import com.unboundid.util.LDAPCommandLineTool;
040import com.unboundid.util.StaticUtils;
041import com.unboundid.util.ThreadSafety;
042import com.unboundid.util.ThreadSafetyLevel;
043import com.unboundid.util.args.ArgumentException;
044import com.unboundid.util.args.ArgumentParser;
045import com.unboundid.util.args.ControlArgument;
046
047
048
049/**
050 * This class provides a simple tool that can be used to perform compare
051 * operations in an LDAP directory server.  All of the necessary information is
052 * provided using command line arguments.    Supported arguments include those
053 * allowed by the {@link LDAPCommandLineTool} class.  In addition, a set of at
054 * least two unnamed trailing arguments must be given.  The first argument
055 * should be a string containing the name of the target attribute followed by a
056 * colon and the assertion value to use for that attribute (e.g.,
057 * "cn:john doe").  Alternately, the attribute name may be followed by two
058 * colons and the base64-encoded representation of the assertion value
059 * (e.g., "cn::  am9obiBkb2U=").  Any subsequent trailing arguments will be the
060 * DN(s) of entries in which to perform the compare operation(s).
061 * <BR><BR>
062 * Some of the APIs demonstrated by this example include:
063 * <UL>
064 *   <LI>Argument Parsing (from the {@code com.unboundid.util.args}
065 *       package)</LI>
066 *   <LI>LDAP Command-Line Tool (from the {@code com.unboundid.util}
067 *       package)</LI>
068 *   <LI>LDAP Communication (from the {@code com.unboundid.ldap.sdk}
069 *       package)</LI>
070 * </UL>
071 */
072@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
073public final class LDAPCompare
074       extends LDAPCommandLineTool
075       implements Serializable
076{
077  /**
078   * The serial version UID for this serializable class.
079   */
080  private static final long serialVersionUID = 719069383330181184L;
081
082
083
084  // The argument parser for this tool.
085  private ArgumentParser parser;
086
087  // The argument used to specify any bind controls that should be used.
088  private ControlArgument bindControls;
089
090  // The argument used to specify any compare controls that should be used.
091  private ControlArgument compareControls;
092
093
094
095  /**
096   * Parse the provided command line arguments and make the appropriate set of
097   * changes.
098   *
099   * @param  args  The command line arguments provided to this program.
100   */
101  public static void main(final String[] args)
102  {
103    final ResultCode resultCode = main(args, System.out, System.err);
104    if (resultCode != ResultCode.SUCCESS)
105    {
106      System.exit(resultCode.intValue());
107    }
108  }
109
110
111
112  /**
113   * Parse the provided command line arguments and make the appropriate set of
114   * changes.
115   *
116   * @param  args       The command line arguments provided to this program.
117   * @param  outStream  The output stream to which standard out should be
118   *                    written.  It may be {@code null} if output should be
119   *                    suppressed.
120   * @param  errStream  The output stream to which standard error should be
121   *                    written.  It may be {@code null} if error messages
122   *                    should be suppressed.
123   *
124   * @return  A result code indicating whether the processing was successful.
125   */
126  public static ResultCode main(final String[] args,
127                                final OutputStream outStream,
128                                final OutputStream errStream)
129  {
130    final LDAPCompare ldapCompare = new LDAPCompare(outStream, errStream);
131    return ldapCompare.runTool(args);
132  }
133
134
135
136  /**
137   * Creates a new instance of this tool.
138   *
139   * @param  outStream  The output stream to which standard out should be
140   *                    written.  It may be {@code null} if output should be
141   *                    suppressed.
142   * @param  errStream  The output stream to which standard error should be
143   *                    written.  It may be {@code null} if error messages
144   *                    should be suppressed.
145   */
146  public LDAPCompare(final OutputStream outStream, final OutputStream errStream)
147  {
148    super(outStream, errStream);
149  }
150
151
152
153  /**
154   * Retrieves the name for this tool.
155   *
156   * @return  The name for this tool.
157   */
158  @Override()
159  public String getToolName()
160  {
161    return "ldapcompare";
162  }
163
164
165
166  /**
167   * Retrieves the description for this tool.
168   *
169   * @return  The description for this tool.
170   */
171  @Override()
172  public String getToolDescription()
173  {
174    return "Process compare operations in LDAP directory server.";
175  }
176
177
178
179  /**
180   * Retrieves the version string for this tool.
181   *
182   * @return  The version string for this tool.
183   */
184  @Override()
185  public String getToolVersion()
186  {
187    return Version.NUMERIC_VERSION_STRING;
188  }
189
190
191
192  /**
193   * Retrieves the maximum number of unnamed trailing arguments that are
194   * allowed.
195   *
196   * @return  A negative value to indicate that any number of trailing arguments
197   *          may be provided.
198   */
199  @Override()
200  public int getMaxTrailingArguments()
201  {
202    return -1;
203  }
204
205
206
207  /**
208   * Retrieves a placeholder string that may be used to indicate what kinds of
209   * trailing arguments are allowed.
210   *
211   * @return  A placeholder string that may be used to indicate what kinds of
212   *          trailing arguments are allowed.
213   */
214  @Override()
215  public String getTrailingArgumentsPlaceholder()
216  {
217    return "attr:value dn1 [dn2 [dn3 [...]]]";
218  }
219
220
221
222  /**
223   * Adds the arguments used by this program that aren't already provided by the
224   * generic {@code LDAPCommandLineTool} framework.
225   *
226   * @param  parser  The argument parser to which the arguments should be added.
227   *
228   * @throws  ArgumentException  If a problem occurs while adding the arguments.
229   */
230  @Override()
231  public void addNonLDAPArguments(final ArgumentParser parser)
232         throws ArgumentException
233  {
234    // Save a reference to the argument parser.
235    this.parser = parser;
236
237    String description =
238         "Information about a control to include in the bind request.";
239    bindControls = new ControlArgument(null, "bindControl", false, 0, null,
240         description);
241    parser.addArgument(bindControls);
242
243
244    description = "Information about a control to include in compare requests.";
245    compareControls = new ControlArgument('J', "control", false, 0, null,
246         description);
247    parser.addArgument(compareControls);
248  }
249
250
251
252  /**
253   * {@inheritDoc}
254   */
255  @Override()
256  protected List<Control> getBindControls()
257  {
258    return bindControls.getValues();
259  }
260
261
262
263  /**
264   * Performs the actual processing for this tool.  In this case, it gets a
265   * connection to the directory server and uses it to perform the requested
266   * comparisons.
267   *
268   * @return  The result code for the processing that was performed.
269   */
270  @Override()
271  public ResultCode doToolProcessing()
272  {
273    // Make sure that at least two trailing arguments were provided, which will
274    // be the attribute value assertion and at least one entry DN.
275    final List<String> trailingArguments = parser.getTrailingArguments();
276    if (trailingArguments.isEmpty())
277    {
278      err("No attribute value assertion was provided.");
279      err();
280      err(parser.getUsageString(79));
281      return ResultCode.PARAM_ERROR;
282    }
283    else if (trailingArguments.size() == 1)
284    {
285      err("No target entry DNs were provided.");
286      err();
287      err(parser.getUsageString(79));
288      return ResultCode.PARAM_ERROR;
289    }
290
291
292    // Parse the attribute value assertion.
293    final String avaString = trailingArguments.get(0);
294    final int colonPos = avaString.indexOf(':');
295    if (colonPos <= 0)
296    {
297      err("Malformed attribute value assertion.");
298      err();
299      err(parser.getUsageString(79));
300      return ResultCode.PARAM_ERROR;
301    }
302
303    final String attributeName = avaString.substring(0, colonPos);
304    final byte[] assertionValueBytes;
305    final int doubleColonPos = avaString.indexOf("::");
306    if (doubleColonPos == colonPos)
307    {
308      // There are two colons, so it's a base64-encoded assertion value.
309      try
310      {
311        assertionValueBytes = Base64.decode(avaString.substring(colonPos+2));
312      }
313      catch (ParseException pe)
314      {
315        err("Unable to base64-decode the assertion value:  ",
316                    pe.getMessage());
317        err();
318        err(parser.getUsageString(79));
319        return ResultCode.PARAM_ERROR;
320      }
321    }
322    else
323    {
324      // There is only a single colon, so it's a simple UTF-8 string.
325      assertionValueBytes =
326           StaticUtils.getBytes(avaString.substring(colonPos+1));
327    }
328
329
330    // Get the connection to the directory server.
331    final LDAPConnection connection;
332    try
333    {
334      connection = getConnection();
335      out("Connected to ", connection.getConnectedAddress(), ':',
336          connection.getConnectedPort());
337    }
338    catch (LDAPException le)
339    {
340      err("Error connecting to the directory server:  ", le.getMessage());
341      return le.getResultCode();
342    }
343
344
345    // For each of the target entry DNs, process the compare.
346    ResultCode resultCode = ResultCode.SUCCESS;
347    CompareRequest compareRequest = null;
348    for (int i=1; i < trailingArguments.size(); i++)
349    {
350      final String targetDN = trailingArguments.get(i);
351      if (compareRequest == null)
352      {
353        compareRequest = new CompareRequest(targetDN, attributeName,
354                                            assertionValueBytes);
355        compareRequest.setControls(compareControls.getValues());
356      }
357      else
358      {
359        compareRequest.setDN(targetDN);
360      }
361
362      try
363      {
364        out("Processing compare request for entry ", targetDN);
365        final CompareResult result = connection.compare(compareRequest);
366        if (result.compareMatched())
367        {
368          out("The compare operation matched.");
369        }
370        else
371        {
372          out("The compare operation did not match.");
373        }
374      }
375      catch (LDAPException le)
376      {
377        resultCode = le.getResultCode();
378        err("An error occurred while processing the request:  ",
379            le.getMessage());
380        err("Result Code:  ", le.getResultCode().intValue(), " (",
381            le.getResultCode().getName(), ')');
382        if (le.getMatchedDN() != null)
383        {
384          err("Matched DN:  ", le.getMatchedDN());
385        }
386        if (le.getReferralURLs() != null)
387        {
388          for (final String url : le.getReferralURLs())
389          {
390            err("Referral URL:  ", url);
391          }
392        }
393      }
394      out();
395    }
396
397
398    // Close the connection to the directory server and exit.
399    connection.close();
400    out();
401    out("Disconnected from the server");
402    return resultCode;
403  }
404
405
406
407  /**
408   * {@inheritDoc}
409   */
410  @Override()
411  public LinkedHashMap<String[],String> getExampleUsages()
412  {
413    final LinkedHashMap<String[],String> examples =
414         new LinkedHashMap<String[],String>();
415
416    final String[] args =
417    {
418      "--hostname", "server.example.com",
419      "--port", "389",
420      "--bindDN", "uid=admin,dc=example,dc=com",
421      "--bindPassword", "password",
422      "givenName:John",
423      "uid=jdoe,ou=People,dc=example,dc=com"
424    };
425    final String description =
426         "Attempt to determine whether the entry for user " +
427         "'uid=jdoe,ou=People,dc=example,dc=com' has a value of 'John' for " +
428         "the givenName attribute.";
429    examples.put(args, description);
430
431    return examples;
432  }
433}