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