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.OutputStream;
026    import java.io.Serializable;
027    import java.text.ParseException;
028    import java.util.LinkedHashMap;
029    import java.util.List;
030    
031    import com.unboundid.ldap.sdk.CompareRequest;
032    import com.unboundid.ldap.sdk.CompareResult;
033    import com.unboundid.ldap.sdk.Control;
034    import com.unboundid.ldap.sdk.LDAPConnection;
035    import com.unboundid.ldap.sdk.LDAPException;
036    import com.unboundid.ldap.sdk.ResultCode;
037    import com.unboundid.ldap.sdk.Version;
038    import com.unboundid.util.Base64;
039    import com.unboundid.util.LDAPCommandLineTool;
040    import com.unboundid.util.StaticUtils;
041    import com.unboundid.util.ThreadSafety;
042    import com.unboundid.util.ThreadSafetyLevel;
043    import com.unboundid.util.args.ArgumentException;
044    import com.unboundid.util.args.ArgumentParser;
045    import 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)
073    public 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    }