001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2006-2008 Sun Microsystems, Inc.
025 *      Portions Copyright 2013-2015 ForgeRock AS.
026 */
027package org.opends.server.tools;
028
029import static com.forgerock.opendj.cli.ArgumentConstants.*;
030import static com.forgerock.opendj.cli.Utils.*;
031
032import static org.opends.messages.ToolMessages.*;
033import static org.opends.server.util.CollectionUtils.*;
034import static org.opends.server.protocols.ldap.LDAPResultCode.*;
035import static org.opends.server.util.StaticUtils.*;
036
037import java.io.BufferedReader;
038import java.io.FileReader;
039import java.io.OutputStream;
040import java.io.PrintStream;
041import java.util.ArrayList;
042import java.util.Iterator;
043import java.util.LinkedHashSet;
044import java.util.LinkedList;
045
046import org.forgerock.i18n.LocalizableMessage;
047import org.forgerock.opendj.ldap.SearchScope;
048import org.opends.server.core.DirectoryServer;
049import org.opends.server.core.DirectoryServer.DirectoryServerVersionHandler;
050import org.opends.server.extensions.ConfigFileHandler;
051import org.opends.server.loggers.JDKLogging;
052import org.opends.server.protocols.ldap.LDAPResultCode;
053import org.opends.server.types.*;
054import org.opends.server.util.BuildVersion;
055import org.opends.server.util.LDIFException;
056import org.opends.server.util.LDIFReader;
057import org.opends.server.util.LDIFWriter;
058
059import com.forgerock.opendj.cli.*;
060
061/**
062 * This class provides a program that may be used to search LDIF files.  It is
063 * modeled after the LDAPSearch tool, with the primary differencing being that
064 * all of its data comes from LDIF rather than communicating over LDAP.
065 * However, it does have a number of differences that allow it to perform
066 * multiple operations in a single pass rather than requiring multiple passes
067 * through the LDIF.
068 */
069public class LDIFSearch
070{
071  /** The fully-qualified name of this class. */
072  private static final String CLASS_NAME = "org.opends.server.tools.LDIFSearch";
073
074  /** The search scope string that will be used for baseObject searches. */
075  private static final String SCOPE_STRING_BASE = "base";
076  /** The search scope string that will be used for singleLevel searches. */
077  private static final String SCOPE_STRING_ONE = "one";
078  /** The search scope string that will be used for wholeSubtree searches. */
079  private static final String SCOPE_STRING_SUB = "sub";
080  /** The search scope string that will be used for subordinateSubtree searches. */
081  private static final String SCOPE_STRING_SUBORDINATE = "subordinate";
082
083  /**
084   * Provides the command line arguments to the <CODE>mainSearch</CODE> method
085   * so that they can be processed.
086   *
087   * @param  args  The command line arguments provided to this program.
088   */
089  public static void main(String[] args)
090  {
091    int exitCode = mainSearch(args, true, System.out, System.err);
092    if (exitCode != 0)
093    {
094      System.exit(filterExitCode(exitCode));
095    }
096  }
097
098
099
100  /**
101   * Parses the provided command line arguments and performs the appropriate
102   * search operation.
103   *
104   * @param  args              The command line arguments provided to this
105   *                           program.
106   * @param  initializeServer  True if server initialization should be done.
107   * @param  outStream         The output stream to use for standard output, or
108   *                           {@code null} if standard output is not needed.
109   * @param  errStream         The output stream to use for standard error, or
110   *                           {@code null} if standard error is not needed.
111   *
112   * @return  The return code for this operation.  A value of zero indicates
113   *          that all processing completed successfully.  A nonzero value
114   *          indicates that some problem occurred during processing.
115   */
116  public static int mainSearch(String[] args, boolean initializeServer,
117                               OutputStream outStream, OutputStream errStream)
118  {
119    PrintStream out = NullOutputStream.wrapOrNullStream(outStream);
120    PrintStream err = NullOutputStream.wrapOrNullStream(errStream);
121    JDKLogging.disableLogging();
122
123    LinkedHashSet<String> scopeStrings = new LinkedHashSet<>(4);
124    scopeStrings.add(SCOPE_STRING_BASE);
125    scopeStrings.add(SCOPE_STRING_ONE);
126    scopeStrings.add(SCOPE_STRING_SUB);
127    scopeStrings.add(SCOPE_STRING_SUBORDINATE);
128
129
130    BooleanArgument     dontWrap;
131    BooleanArgument     overwriteExisting;
132    BooleanArgument     showUsage;
133    StringArgument      filterFile;
134    IntegerArgument     sizeLimit;
135    IntegerArgument     timeLimit;
136    MultiChoiceArgument<String> scopeString;
137    StringArgument      baseDNString;
138    StringArgument      configClass;
139    StringArgument      configFile;
140    StringArgument      ldifFile;
141    StringArgument      outputFile;
142
143
144    LocalizableMessage toolDescription = INFO_LDIFSEARCH_TOOL_DESCRIPTION.get();
145    ArgumentParser argParser = new ArgumentParser(CLASS_NAME, toolDescription,
146                                                  false, true, 0, 0,
147                                                  "[filter] [attributes ...]");
148    argParser.setShortToolDescription(REF_SHORT_DESC_LDIFSEARCH.get());
149    argParser.setVersionHandler(new DirectoryServerVersionHandler());
150
151    try
152    {
153      ldifFile = new StringArgument(
154              "ldiffile", 'l', "ldifFile", false, true,
155              true, INFO_LDIFFILE_PLACEHOLDER.get(), null, null,
156              INFO_LDIFSEARCH_DESCRIPTION_LDIF_FILE.get());
157      argParser.addArgument(ldifFile);
158
159      baseDNString = new StringArgument(
160              "basedn", OPTION_SHORT_BASEDN,
161              OPTION_LONG_BASEDN, false, true,
162              true, INFO_BASEDN_PLACEHOLDER.get(), "", null,
163              INFO_LDIFSEARCH_DESCRIPTION_BASEDN.get());
164      argParser.addArgument(baseDNString);
165
166      scopeString = new MultiChoiceArgument<>(
167              "scope", 's', "searchScope", false, false,
168              true, INFO_SCOPE_PLACEHOLDER.get(), SCOPE_STRING_SUB,
169              null, scopeStrings, false,
170              INFO_LDIFSEARCH_DESCRIPTION_SCOPE.get());
171      argParser.addArgument(scopeString);
172
173      configFile = new StringArgument(
174              "configfile", 'c', "configFile", false,
175              false, true, INFO_CONFIGFILE_PLACEHOLDER.get(), null, null,
176              INFO_DESCRIPTION_CONFIG_FILE.get());
177      configFile.setHidden(true);
178      argParser.addArgument(configFile);
179
180      configClass = new StringArgument("configclass", OPTION_SHORT_CONFIG_CLASS,
181                             OPTION_LONG_CONFIG_CLASS, false,
182                             false, true, INFO_CONFIGCLASS_PLACEHOLDER.get(),
183                             ConfigFileHandler.class.getName(), null,
184                             INFO_DESCRIPTION_CONFIG_CLASS.get());
185      configClass.setHidden(true);
186      argParser.addArgument(configClass);
187
188      filterFile = new StringArgument("filterfile", 'f', "filterFile", false,
189          false, true, INFO_FILTER_FILE_PLACEHOLDER.get(), null, null,
190          INFO_LDIFSEARCH_DESCRIPTION_FILTER_FILE.get());
191      argParser.addArgument(filterFile);
192
193      outputFile = new StringArgument(
194              "outputfile", 'o', "outputFile", false,
195              false, true, INFO_OUTPUT_FILE_PLACEHOLDER.get(), null, null,
196              INFO_LDIFSEARCH_DESCRIPTION_OUTPUT_FILE.get());
197      argParser.addArgument(outputFile);
198
199      overwriteExisting =
200           new BooleanArgument(
201                   "overwriteexisting", 'O',"overwriteExisting",
202                   INFO_LDIFSEARCH_DESCRIPTION_OVERWRITE_EXISTING.get());
203      argParser.addArgument(overwriteExisting);
204
205      dontWrap = new BooleanArgument(
206              "dontwrap", 'T', "dontWrap",
207              INFO_LDIFSEARCH_DESCRIPTION_DONT_WRAP.get());
208      argParser.addArgument(dontWrap);
209
210      sizeLimit = new IntegerArgument(
211              "sizelimit", 'z', "sizeLimit", false,
212              false, true, INFO_SIZE_LIMIT_PLACEHOLDER.get(), 0, null,
213              true, 0, false, 0,
214              INFO_LDIFSEARCH_DESCRIPTION_SIZE_LIMIT.get());
215      argParser.addArgument(sizeLimit);
216
217      timeLimit = new IntegerArgument(
218              "timelimit", 't', "timeLimit", false,
219              false, true, INFO_TIME_LIMIT_PLACEHOLDER.get(), 0, null,
220              true, 0, false, 0,
221              INFO_LDIFSEARCH_DESCRIPTION_TIME_LIMIT.get());
222      argParser.addArgument(timeLimit);
223
224
225      showUsage = CommonArguments.getShowUsage();
226      argParser.addArgument(showUsage);
227      argParser.setUsageArgument(showUsage);
228    }
229    catch (ArgumentException ae)
230    {
231      printWrappedText(err, ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage()));
232      return 1;
233    }
234
235
236    // Parse the command-line arguments provided to the program.
237    try
238    {
239      argParser.parseArguments(args);
240    }
241    catch (ArgumentException ae)
242    {
243      argParser.displayMessageAndUsageReference(err, ERR_ERROR_PARSING_ARGS.get(ae.getMessage()));
244      return CLIENT_SIDE_PARAM_ERROR;
245    }
246
247
248    // If we should just display usage or version information,
249    // then print it and exit.
250    if (argParser.usageOrVersionDisplayed())
251    {
252      return 0;
253    }
254
255    // Checks the version - if upgrade required, the tool is unusable
256    try
257    {
258      BuildVersion.checkVersionMismatch();
259    }
260    catch (InitializationException e)
261    {
262      printWrappedText(err, e.getMessage());
263      return 1;
264    }
265
266    // Make sure that at least one filter was provided.  Also get the attribute
267    // list at the same time because it may need to be specified in the same
268    // way.
269    boolean            allUserAttrs        = false;
270    boolean            allOperationalAttrs = false;
271    //Return objectclass attribute unless analysis of the arguments determines
272    //otherwise.
273    boolean            includeObjectclassAttrs = true;
274    final LinkedList<String> attributeNames = new LinkedList<>();
275    LinkedList<String> objectClassNames = new LinkedList<>();
276    LinkedList<String> filterStrings = new LinkedList<>();
277    if (filterFile.isPresent())
278    {
279      BufferedReader in = null;
280      try
281      {
282        String fileNameValue = filterFile.getValue();
283        in = new BufferedReader(new FileReader(fileNameValue));
284        String line = null;
285
286        while ((line = in.readLine()) != null)
287        {
288          if(line.trim().equals(""))
289          {
290            // ignore empty lines.
291            continue;
292          }
293          filterStrings.add(line);
294        }
295      } catch(Exception e)
296      {
297        printWrappedText(err, e.getMessage());
298        return 1;
299      }
300      finally
301      {
302        close(in);
303      }
304
305      ArrayList<String> trailingArguments = argParser.getTrailingArguments();
306      if (trailingArguments != null && !trailingArguments.isEmpty())
307      {
308        for (String attributeName : trailingArguments)
309        {
310          String lowerName = toLowerCase(attributeName);
311          if (lowerName.equals("*"))
312          {
313            allUserAttrs = true;
314          }
315          else if (lowerName.equals("+"))
316          {
317            allOperationalAttrs = true;
318          }
319          else if (lowerName.startsWith("@"))
320          {
321            objectClassNames.add(lowerName.substring(1));
322          }
323          else
324          {
325            attributeNames.add(lowerName);
326          }
327        }
328      }
329    }
330    else
331    {
332      ArrayList<String> trailingArguments = argParser.getTrailingArguments();
333      if (trailingArguments == null || trailingArguments.isEmpty())
334      {
335        argParser.displayMessageAndUsageReference(err, ERR_LDIFSEARCH_NO_FILTER.get());
336        return 1;
337      }
338
339      Iterator<String> iterator = trailingArguments.iterator();
340      filterStrings = newLinkedList(iterator.next());
341
342      while (iterator.hasNext())
343      {
344        String lowerName = toLowerCase(iterator.next());
345        if (lowerName.equals("*"))
346        {
347          allUserAttrs = true;
348        }
349        else if (lowerName.equals("+"))
350        {
351          allOperationalAttrs = true;
352        }
353        else if (lowerName.startsWith("@"))
354        {
355          objectClassNames.add(lowerName.substring(1));
356        }
357        else
358        {
359          attributeNames.add(lowerName);
360        }
361      }
362    }
363
364    if (attributeNames.isEmpty()
365        && objectClassNames.isEmpty()
366        && !allOperationalAttrs)
367    {
368      // This will be true if no attributes were requested, which is effectively
369      // all user attributes.  It will also be true if just "*" was included,
370      // but the net result will be the same.
371      allUserAttrs = true;
372    }
373
374    //Determine if objectclass attribute should be returned.
375    if(!allUserAttrs) {
376      //Single '+', never return objectclass.
377      if(allOperationalAttrs && objectClassNames.isEmpty() &&
378         attributeNames.isEmpty())
379      {
380        includeObjectclassAttrs=false;
381      }
382      //If "objectclass" isn't specified in the attributes to return, then
383      //don't include objectclass attribute.
384      if(!attributeNames.isEmpty() && objectClassNames.isEmpty() &&
385         !attributeNames.contains("objectclass"))
386      {
387        includeObjectclassAttrs=false;
388      }
389    }
390
391
392    // Bootstrap the Directory Server configuration for use as a client.
393    DirectoryServer directoryServer = DirectoryServer.getInstance();
394
395    // If we're to use the configuration then initialize it, along with the
396    // schema.
397    boolean checkSchema = configFile.isPresent();
398
399    if (initializeServer)
400    {
401      DirectoryServer.bootstrapClient();
402
403      if (checkSchema)
404      {
405        try
406        {
407          DirectoryServer.initializeJMX();
408        }
409        catch (Exception e)
410        {
411          printWrappedText(err, ERR_LDIFSEARCH_CANNOT_INITIALIZE_JMX.get(configFile.getValue(), e.getMessage()));
412          return 1;
413        }
414
415        try
416        {
417          directoryServer.initializeConfiguration(configClass.getValue(), configFile.getValue());
418        }
419        catch (Exception e)
420        {
421          printWrappedText(err, ERR_LDIFSEARCH_CANNOT_INITIALIZE_CONFIG.get(configFile.getValue(), e.getMessage()));
422          return 1;
423        }
424
425        try
426        {
427          directoryServer.initializeSchema();
428        }
429        catch (Exception e)
430        {
431          printWrappedText(err, ERR_LDIFSEARCH_CANNOT_INITIALIZE_SCHEMA.get(configFile.getValue(), e.getMessage()));
432          return 1;
433        }
434      }
435    }
436
437    // Choose the desired search scope.
438    SearchScope searchScope;
439    if (scopeString.isPresent())
440    {
441      String scopeStr = toLowerCase(scopeString.getValue());
442      if (scopeStr.equals(SCOPE_STRING_BASE))
443      {
444        searchScope = SearchScope.BASE_OBJECT;
445      }
446      else if (scopeStr.equals(SCOPE_STRING_ONE))
447      {
448        searchScope = SearchScope.SINGLE_LEVEL;
449      }
450      else if (scopeStr.equals(SCOPE_STRING_SUBORDINATE))
451      {
452        searchScope = SearchScope.SUBORDINATES;
453      }
454      else
455      {
456        searchScope = SearchScope.WHOLE_SUBTREE;
457      }
458    }
459    else
460    {
461      searchScope = SearchScope.WHOLE_SUBTREE;
462    }
463
464
465    // Create the list of filters that will be used to process the searches.
466    LinkedList<SearchFilter> searchFilters = new LinkedList<>();
467    for (String filterString : filterStrings)
468    {
469      try
470      {
471        searchFilters.add(SearchFilter.createFilterFromString(filterString));
472      }
473      catch (Exception e)
474      {
475        printWrappedText(err, ERR_LDIFSEARCH_CANNOT_PARSE_FILTER.get(filterString, e.getMessage()));
476        return 1;
477      }
478    }
479
480
481    // Transform the attributes to return from strings to attribute types.
482    LinkedHashSet<AttributeType> userAttributeTypes = new LinkedHashSet<>();
483    LinkedHashSet<AttributeType> operationalAttributeTypes = new LinkedHashSet<>();
484    for (String attributeName : attributeNames)
485    {
486      AttributeType t = DirectoryServer.getAttributeTypeOrDefault(attributeName);
487      if (t.isOperational())
488      {
489        operationalAttributeTypes.add(t);
490      }
491      else
492      {
493        userAttributeTypes.add(t);
494      }
495    }
496
497    for (String objectClassName : objectClassNames)
498    {
499      ObjectClass c = DirectoryServer.getObjectClass(objectClassName, true);
500      for (AttributeType t : c.getRequiredAttributeChain())
501      {
502        if (t.isOperational())
503        {
504          operationalAttributeTypes.add(t);
505        }
506        else
507        {
508          userAttributeTypes.add(t);
509        }
510      }
511
512      for (AttributeType t : c.getOptionalAttributeChain())
513      {
514        if (t.isOperational())
515        {
516          operationalAttributeTypes.add(t);
517        }
518        else
519        {
520          userAttributeTypes.add(t);
521        }
522      }
523    }
524
525
526    // Set the base DNs for the import config.
527    LinkedList<DN> baseDNs = new LinkedList<>();
528    if (baseDNString.isPresent())
529    {
530      for (String dnString : baseDNString.getValues())
531      {
532        try
533        {
534          baseDNs.add(DN.valueOf(dnString));
535        }
536        catch (Exception e)
537        {
538          printWrappedText(err, ERR_LDIFSEARCH_CANNOT_PARSE_BASE_DN.get(dnString, e.getMessage()));
539          return 1;
540        }
541      }
542    }
543    else
544    {
545      baseDNs.add(DN.rootDN());
546    }
547
548
549    // Get the time limit in milliseconds.
550    long timeLimitMillis;
551    try
552    {
553      if (timeLimit.isPresent())
554      {
555        timeLimitMillis = 1000L * timeLimit.getIntValue();
556      }
557      else
558      {
559        timeLimitMillis = 0;
560      }
561    }
562    catch (Exception e)
563    {
564      printWrappedText(err, ERR_LDIFSEARCH_CANNOT_PARSE_TIME_LIMIT.get(e));
565      return 1;
566    }
567
568
569    // Convert the size limit to an integer.
570    int sizeLimitValue;
571    try
572    {
573      if (sizeLimit.isPresent())
574      {
575        sizeLimitValue = sizeLimit.getIntValue();
576      }
577      else
578      {
579        sizeLimitValue =0;
580      }
581    }
582    catch (Exception e)
583    {
584      printWrappedText(err, ERR_LDIFSEARCH_CANNOT_PARSE_SIZE_LIMIT.get(e));
585      return 1;
586    }
587
588
589    // Create the LDIF import configuration that will be used to read the source
590    // data.
591    LDIFImportConfig importConfig;
592    if (ldifFile.isPresent())
593    {
594      importConfig = new LDIFImportConfig(ldifFile.getValues());
595    }
596    else
597    {
598      importConfig = new LDIFImportConfig(System.in);
599    }
600
601
602    // Create the LDIF export configuration that will be used to write the
603    // matching entries.
604    LDIFExportConfig exportConfig;
605    if (outputFile.isPresent())
606    {
607      if (overwriteExisting.isPresent())
608      {
609        exportConfig = new LDIFExportConfig(outputFile.getValue(),
610                                            ExistingFileBehavior.OVERWRITE);
611      }
612      else
613      {
614        exportConfig = new LDIFExportConfig(outputFile.getValue(),
615                                            ExistingFileBehavior.APPEND);
616      }
617    }
618    else
619    {
620      exportConfig = new LDIFExportConfig(out);
621    }
622
623    exportConfig.setIncludeObjectClasses(includeObjectclassAttrs);
624    if (dontWrap.isPresent())
625    {
626      exportConfig.setWrapColumn(0);
627    }
628    else
629    {
630      exportConfig.setWrapColumn(75);
631    }
632
633
634    // Create the LDIF reader/writer from the import/export config.
635    LDIFReader reader;
636    LDIFWriter writer;
637    try
638    {
639      reader = new LDIFReader(importConfig);
640    }
641    catch (Exception e)
642    {
643      printWrappedText(err, ERR_LDIFSEARCH_CANNOT_CREATE_READER.get(e));
644      return 1;
645    }
646
647    try
648    {
649      writer = new LDIFWriter(exportConfig);
650    }
651    catch (Exception e)
652    {
653      close(reader);
654      printWrappedText(err, ERR_LDIFSEARCH_CANNOT_CREATE_WRITER.get(e));
655      return 1;
656    }
657
658
659    // Start reading data from the LDIF reader.
660    long startTime  = System.currentTimeMillis();
661    long stopTime   = startTime + timeLimitMillis;
662    long matchCount = 0;
663    int  resultCode = LDAPResultCode.SUCCESS;
664    while (true)
665    {
666      // If the time limit has been reached, then stop now.
667      if (timeLimitMillis > 0 && System.currentTimeMillis() > stopTime)
668      {
669        resultCode = LDAPResultCode.TIME_LIMIT_EXCEEDED;
670
671        LocalizableMessage message = WARN_LDIFSEARCH_TIME_LIMIT_EXCEEDED.get();
672        err.println(message);
673        break;
674      }
675
676
677      try
678      {
679        Entry entry = reader.readEntry(checkSchema);
680        if (entry == null)
681        {
682          break;
683        }
684
685
686        // Check to see if the entry has an acceptable base and scope.
687        boolean matchesBaseAndScope = false;
688        for (DN baseDN : baseDNs)
689        {
690          if (entry.matchesBaseAndScope(baseDN, searchScope))
691          {
692            matchesBaseAndScope = true;
693            break;
694          }
695        }
696
697        if (! matchesBaseAndScope)
698        {
699          continue;
700        }
701
702
703        // Check to see if the entry matches any of the filters.
704        boolean matchesFilter = false;
705        for (SearchFilter filter : searchFilters)
706        {
707          if (filter.matchesEntry(entry))
708          {
709            matchesFilter = true;
710            break;
711          }
712        }
713
714        if (! matchesFilter)
715        {
716          continue;
717        }
718
719
720        // Prepare the entry to return to the client.
721        if (! allUserAttrs)
722        {
723          Iterator<AttributeType> iterator =
724               entry.getUserAttributes().keySet().iterator();
725          while (iterator.hasNext())
726          {
727            if (! userAttributeTypes.contains(iterator.next()))
728            {
729              iterator.remove();
730            }
731          }
732        }
733
734        if (! allOperationalAttrs)
735        {
736          Iterator<AttributeType> iterator =
737               entry.getOperationalAttributes().keySet().iterator();
738          while (iterator.hasNext())
739          {
740            if (! operationalAttributeTypes.contains(iterator.next()))
741            {
742              iterator.remove();
743            }
744          }
745        }
746
747
748        // Write the entry to the client and increase the count.
749        // FIXME -- Should we include a comment about which base+filter matched?
750        writer.writeEntry(entry);
751        writer.flush();
752
753        matchCount++;
754        if (sizeLimitValue > 0 && matchCount >= sizeLimitValue)
755        {
756          resultCode = LDAPResultCode.SIZE_LIMIT_EXCEEDED;
757
758          LocalizableMessage message = WARN_LDIFSEARCH_SIZE_LIMIT_EXCEEDED.get();
759          err.println(message);
760          break;
761        }
762      }
763      catch (LDIFException le)
764      {
765        if (le.canContinueReading())
766        {
767          LocalizableMessage message = ERR_LDIFSEARCH_CANNOT_READ_ENTRY_RECOVERABLE.get(
768                  le.getMessage());
769          err.println(message);
770        }
771        else
772        {
773          LocalizableMessage message = ERR_LDIFSEARCH_CANNOT_READ_ENTRY_FATAL.get(
774                  le.getMessage());
775          err.println(message);
776          resultCode = LDAPResultCode.CLIENT_SIDE_LOCAL_ERROR;
777          break;
778        }
779      }
780      catch (Exception e)
781      {
782        err.println(ERR_LDIFSEARCH_ERROR_DURING_PROCESSING.get(e));
783        resultCode = LDAPResultCode.CLIENT_SIDE_LOCAL_ERROR;
784        break;
785      }
786    }
787
788    close(reader, writer);
789
790    return resultCode;
791  }
792}
793