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