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.loggers.JDKLogging;
043import org.opends.server.protocols.ldap.LDAPResultCode;
044import org.forgerock.opendj.ldap.schema.AttributeType;
045import org.forgerock.opendj.ldap.schema.ObjectClass;
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      configFile;
132    StringArgument      ldifFile;
133    StringArgument      outputFile;
134
135
136    LocalizableMessage toolDescription = INFO_LDIFSEARCH_TOOL_DESCRIPTION.get();
137    ArgumentParser argParser = new ArgumentParser(CLASS_NAME, toolDescription,
138                                                  false, true, 0, 0,
139                                                  "[filter] [attributes ...]");
140    argParser.setShortToolDescription(REF_SHORT_DESC_LDIFSEARCH.get());
141    argParser.setVersionHandler(new DirectoryServerVersionHandler());
142
143    try
144    {
145      ldifFile =
146              StringArgument.builder("ldifFile")
147                      .shortIdentifier('l')
148                      .description(INFO_LDIFSEARCH_DESCRIPTION_LDIF_FILE.get())
149                      .multiValued()
150                      .valuePlaceholder(INFO_LDIFFILE_PLACEHOLDER.get())
151                      .buildAndAddToParser(argParser);
152      baseDNString =
153              StringArgument.builder(OPTION_LONG_BASEDN)
154                      .shortIdentifier(OPTION_SHORT_BASEDN)
155                      .description(INFO_LDIFSEARCH_DESCRIPTION_BASEDN.get())
156                      .multiValued()
157                      .defaultValue("")
158                      .valuePlaceholder(INFO_BASEDN_PLACEHOLDER.get())
159                      .buildAndAddToParser(argParser);
160      scopeString =
161              MultiChoiceArgument.<String>builder("searchScope")
162                      .shortIdentifier('s')
163                      .description(INFO_LDIFSEARCH_DESCRIPTION_SCOPE.get())
164                      .allowedValues(scopeStrings)
165                      .defaultValue(SCOPE_STRING_SUB)
166                      .valuePlaceholder(INFO_SCOPE_PLACEHOLDER.get())
167                      .buildAndAddToParser(argParser);
168      configFile =
169              StringArgument.builder("configFile")
170                      .shortIdentifier('c')
171                      .description(INFO_DESCRIPTION_CONFIG_FILE.get())
172                      .hidden()
173                      .valuePlaceholder(INFO_CONFIGFILE_PLACEHOLDER.get())
174                      .buildAndAddToParser(argParser);
175      filterFile =
176              StringArgument.builder("filterFile")
177                      .shortIdentifier('f')
178                      .description(INFO_LDIFSEARCH_DESCRIPTION_FILTER_FILE.get())
179                      .valuePlaceholder(INFO_FILTER_FILE_PLACEHOLDER.get())
180                      .buildAndAddToParser(argParser);
181      outputFile =
182              StringArgument.builder("outputFile")
183                      .shortIdentifier('o')
184                      .description(INFO_LDIFSEARCH_DESCRIPTION_OUTPUT_FILE.get())
185                      .valuePlaceholder(INFO_OUTPUT_FILE_PLACEHOLDER.get())
186                      .buildAndAddToParser(argParser);
187      overwriteExisting =
188              BooleanArgument.builder("overwriteExisting")
189                      .shortIdentifier('O')
190                      .description(INFO_LDIFSEARCH_DESCRIPTION_OVERWRITE_EXISTING.get())
191                      .buildAndAddToParser(argParser);
192      dontWrap =
193              BooleanArgument.builder("dontWrap")
194                      .shortIdentifier('T')
195                      .description(INFO_LDIFSEARCH_DESCRIPTION_DONT_WRAP.get())
196                      .buildAndAddToParser(argParser);
197      sizeLimit =
198              IntegerArgument.builder("sizeLimit")
199                      .shortIdentifier('z')
200                      .description(INFO_LDIFSEARCH_DESCRIPTION_SIZE_LIMIT.get())
201                      .lowerBound(0)
202                      .defaultValue(0)
203                      .valuePlaceholder(INFO_SIZE_LIMIT_PLACEHOLDER.get())
204                      .buildAndAddToParser(argParser);
205      timeLimit =
206              IntegerArgument.builder("timeLimit")
207                      .shortIdentifier('t')
208                      .description(INFO_LDIFSEARCH_DESCRIPTION_TIME_LIMIT.get())
209                      .lowerBound(0)
210                      .defaultValue(0)
211                      .valuePlaceholder(INFO_TIME_LIMIT_PLACEHOLDER.get())
212                      .buildAndAddToParser(argParser);
213
214      showUsage = showUsageArgument();
215      argParser.addArgument(showUsage);
216      argParser.setUsageArgument(showUsage);
217    }
218    catch (ArgumentException ae)
219    {
220      printWrappedText(err, ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage()));
221      return 1;
222    }
223
224
225    // Parse the command-line arguments provided to the program.
226    try
227    {
228      argParser.parseArguments(args);
229    }
230    catch (ArgumentException ae)
231    {
232      argParser.displayMessageAndUsageReference(err, ERR_ERROR_PARSING_ARGS.get(ae.getMessage()));
233      return CLIENT_SIDE_PARAM_ERROR;
234    }
235
236
237    // If we should just display usage or version information,
238    // then print it and exit.
239    if (argParser.usageOrVersionDisplayed())
240    {
241      return 0;
242    }
243
244    // Checks the version - if upgrade required, the tool is unusable
245    try
246    {
247      BuildVersion.checkVersionMismatch();
248    }
249    catch (InitializationException e)
250    {
251      printWrappedText(err, e.getMessage());
252      return 1;
253    }
254
255    // Make sure that at least one filter was provided.  Also get the attribute
256    // list at the same time because it may need to be specified in the same
257    // way.
258    boolean            allUserAttrs        = false;
259    boolean            allOperationalAttrs = false;
260    //Return objectclass attribute unless analysis of the arguments determines
261    //otherwise.
262    boolean            includeObjectclassAttrs = true;
263    final LinkedList<String> attributeNames = new LinkedList<>();
264    LinkedList<String> objectClassNames = new LinkedList<>();
265    LinkedList<String> filterStrings = new LinkedList<>();
266    if (filterFile.isPresent())
267    {
268      BufferedReader in = null;
269      try
270      {
271        String fileNameValue = filterFile.getValue();
272        in = new BufferedReader(new FileReader(fileNameValue));
273        String line = null;
274
275        while ((line = in.readLine()) != null)
276        {
277          if(line.trim().equals(""))
278          {
279            // ignore empty lines.
280            continue;
281          }
282          filterStrings.add(line);
283        }
284      } catch(Exception e)
285      {
286        printWrappedText(err, e.getMessage());
287        return 1;
288      }
289      finally
290      {
291        close(in);
292      }
293
294      ArrayList<String> trailingArguments = argParser.getTrailingArguments();
295      if (trailingArguments != null && !trailingArguments.isEmpty())
296      {
297        for (String attributeName : trailingArguments)
298        {
299          String lowerName = toLowerCase(attributeName);
300          if (lowerName.equals("*"))
301          {
302            allUserAttrs = true;
303          }
304          else if (lowerName.equals("+"))
305          {
306            allOperationalAttrs = true;
307          }
308          else if (lowerName.startsWith("@"))
309          {
310            objectClassNames.add(lowerName.substring(1));
311          }
312          else
313          {
314            attributeNames.add(lowerName);
315          }
316        }
317      }
318    }
319    else
320    {
321      ArrayList<String> trailingArguments = argParser.getTrailingArguments();
322      if (trailingArguments == null || trailingArguments.isEmpty())
323      {
324        argParser.displayMessageAndUsageReference(err, ERR_LDIFSEARCH_NO_FILTER.get());
325        return 1;
326      }
327
328      Iterator<String> iterator = trailingArguments.iterator();
329      filterStrings = newLinkedList(iterator.next());
330
331      while (iterator.hasNext())
332      {
333        String lowerName = toLowerCase(iterator.next());
334        if (lowerName.equals("*"))
335        {
336          allUserAttrs = true;
337        }
338        else if (lowerName.equals("+"))
339        {
340          allOperationalAttrs = true;
341        }
342        else if (lowerName.startsWith("@"))
343        {
344          objectClassNames.add(lowerName.substring(1));
345        }
346        else
347        {
348          attributeNames.add(lowerName);
349        }
350      }
351    }
352
353    if (attributeNames.isEmpty()
354        && objectClassNames.isEmpty()
355        && !allOperationalAttrs)
356    {
357      // This will be true if no attributes were requested, which is effectively
358      // all user attributes.  It will also be true if just "*" was included,
359      // but the net result will be the same.
360      allUserAttrs = true;
361    }
362
363    //Determine if objectclass attribute should be returned.
364    if(!allUserAttrs) {
365      //Single '+', never return objectclass.
366      if(allOperationalAttrs && objectClassNames.isEmpty() &&
367         attributeNames.isEmpty())
368      {
369        includeObjectclassAttrs=false;
370      }
371      //If "objectclass" isn't specified in the attributes to return, then
372      //don't include objectclass attribute.
373      if(!attributeNames.isEmpty() && objectClassNames.isEmpty() &&
374         !attributeNames.contains("objectclass"))
375      {
376        includeObjectclassAttrs=false;
377      }
378    }
379
380
381    // Bootstrap the Directory Server configuration for use as a client.
382    DirectoryServer directoryServer = DirectoryServer.getInstance();
383
384    // If we're to use the configuration then initialize it, along with the
385    // schema.
386    boolean checkSchema = configFile.isPresent();
387
388    if (initializeServer)
389    {
390      DirectoryServer.bootstrapClient();
391
392      if (checkSchema)
393      {
394        try
395        {
396          DirectoryServer.initializeJMX();
397        }
398        catch (Exception e)
399        {
400          printWrappedText(err, ERR_LDIFSEARCH_CANNOT_INITIALIZE_JMX.get(configFile.getValue(), e.getMessage()));
401          return 1;
402        }
403
404        try
405        {
406          directoryServer.initializeConfiguration(configFile.getValue());
407        }
408        catch (Exception e)
409        {
410          printWrappedText(err, ERR_LDIFSEARCH_CANNOT_INITIALIZE_CONFIG.get(configFile.getValue(), e.getMessage()));
411          return 1;
412        }
413
414        try
415        {
416          directoryServer.initializeSchema();
417        }
418        catch (Exception e)
419        {
420          printWrappedText(err, ERR_LDIFSEARCH_CANNOT_INITIALIZE_SCHEMA.get(configFile.getValue(), e.getMessage()));
421          return 1;
422        }
423      }
424    }
425
426    // Choose the desired search scope.
427    SearchScope searchScope;
428    if (scopeString.isPresent())
429    {
430      String scopeStr = toLowerCase(scopeString.getValue());
431      if (scopeStr.equals(SCOPE_STRING_BASE))
432      {
433        searchScope = SearchScope.BASE_OBJECT;
434      }
435      else if (scopeStr.equals(SCOPE_STRING_ONE))
436      {
437        searchScope = SearchScope.SINGLE_LEVEL;
438      }
439      else if (scopeStr.equals(SCOPE_STRING_SUBORDINATE))
440      {
441        searchScope = SearchScope.SUBORDINATES;
442      }
443      else
444      {
445        searchScope = SearchScope.WHOLE_SUBTREE;
446      }
447    }
448    else
449    {
450      searchScope = SearchScope.WHOLE_SUBTREE;
451    }
452
453
454    // Create the list of filters that will be used to process the searches.
455    LinkedList<SearchFilter> searchFilters = new LinkedList<>();
456    for (String filterString : filterStrings)
457    {
458      try
459      {
460        searchFilters.add(SearchFilter.createFilterFromString(filterString));
461      }
462      catch (Exception e)
463      {
464        printWrappedText(err, ERR_LDIFSEARCH_CANNOT_PARSE_FILTER.get(filterString, e.getMessage()));
465        return 1;
466      }
467    }
468
469
470    // Transform the attributes to return from strings to attribute types.
471    LinkedHashSet<AttributeType> userAttributeTypes = new LinkedHashSet<>();
472    LinkedHashSet<AttributeType> operationalAttributeTypes = new LinkedHashSet<>();
473    for (String attributeName : attributeNames)
474    {
475      AttributeType t = DirectoryServer.getSchema().getAttributeType(attributeName);
476      if (t.isOperational())
477      {
478        operationalAttributeTypes.add(t);
479      }
480      else
481      {
482        userAttributeTypes.add(t);
483      }
484    }
485
486    for (String objectClassName : objectClassNames)
487    {
488      ObjectClass c = DirectoryServer.getSchema().getObjectClass(objectClassName);
489      for (AttributeType t : c.getRequiredAttributes())
490      {
491        if (t.isOperational())
492        {
493          operationalAttributeTypes.add(t);
494        }
495        else
496        {
497          userAttributeTypes.add(t);
498        }
499      }
500
501      for (AttributeType t : c.getOptionalAttributes())
502      {
503        if (t.isOperational())
504        {
505          operationalAttributeTypes.add(t);
506        }
507        else
508        {
509          userAttributeTypes.add(t);
510        }
511      }
512    }
513
514
515    // Set the base DNs for the import config.
516    LinkedList<DN> baseDNs = new LinkedList<>();
517    if (baseDNString.isPresent())
518    {
519      for (String dnString : baseDNString.getValues())
520      {
521        try
522        {
523          baseDNs.add(DN.valueOf(dnString));
524        }
525        catch (Exception e)
526        {
527          printWrappedText(err, ERR_LDIFSEARCH_CANNOT_PARSE_BASE_DN.get(dnString, e.getMessage()));
528          return 1;
529        }
530      }
531    }
532    else
533    {
534      baseDNs.add(DN.rootDN());
535    }
536
537
538    // Get the time limit in milliseconds.
539    long timeLimitMillis;
540    try
541    {
542      if (timeLimit.isPresent())
543      {
544        timeLimitMillis = 1000L * timeLimit.getIntValue();
545      }
546      else
547      {
548        timeLimitMillis = 0;
549      }
550    }
551    catch (Exception e)
552    {
553      printWrappedText(err, ERR_LDIFSEARCH_CANNOT_PARSE_TIME_LIMIT.get(e));
554      return 1;
555    }
556
557
558    // Convert the size limit to an integer.
559    int sizeLimitValue;
560    try
561    {
562      if (sizeLimit.isPresent())
563      {
564        sizeLimitValue = sizeLimit.getIntValue();
565      }
566      else
567      {
568        sizeLimitValue =0;
569      }
570    }
571    catch (Exception e)
572    {
573      printWrappedText(err, ERR_LDIFSEARCH_CANNOT_PARSE_SIZE_LIMIT.get(e));
574      return 1;
575    }
576
577
578    // Create the LDIF import configuration that will be used to read the source
579    // data.
580    LDIFImportConfig importConfig;
581    if (ldifFile.isPresent())
582    {
583      importConfig = new LDIFImportConfig(ldifFile.getValues());
584    }
585    else
586    {
587      importConfig = new LDIFImportConfig(System.in);
588    }
589
590
591    // Create the LDIF export configuration that will be used to write the
592    // matching entries.
593    LDIFExportConfig exportConfig;
594    if (outputFile.isPresent())
595    {
596      if (overwriteExisting.isPresent())
597      {
598        exportConfig = new LDIFExportConfig(outputFile.getValue(),
599                                            ExistingFileBehavior.OVERWRITE);
600      }
601      else
602      {
603        exportConfig = new LDIFExportConfig(outputFile.getValue(),
604                                            ExistingFileBehavior.APPEND);
605      }
606    }
607    else
608    {
609      exportConfig = new LDIFExportConfig(out);
610    }
611
612    exportConfig.setIncludeObjectClasses(includeObjectclassAttrs);
613    if (dontWrap.isPresent())
614    {
615      exportConfig.setWrapColumn(0);
616    }
617    else
618    {
619      exportConfig.setWrapColumn(75);
620    }
621
622
623    // Create the LDIF reader/writer from the import/export config.
624    LDIFReader reader;
625    LDIFWriter writer;
626    try
627    {
628      reader = new LDIFReader(importConfig);
629    }
630    catch (Exception e)
631    {
632      printWrappedText(err, ERR_LDIFSEARCH_CANNOT_CREATE_READER.get(e));
633      return 1;
634    }
635
636    try
637    {
638      writer = new LDIFWriter(exportConfig);
639    }
640    catch (Exception e)
641    {
642      close(reader);
643      printWrappedText(err, ERR_LDIFSEARCH_CANNOT_CREATE_WRITER.get(e));
644      return 1;
645    }
646
647
648    // Start reading data from the LDIF reader.
649    long startTime  = System.currentTimeMillis();
650    long stopTime   = startTime + timeLimitMillis;
651    long matchCount = 0;
652    int  resultCode = LDAPResultCode.SUCCESS;
653    while (true)
654    {
655      // If the time limit has been reached, then stop now.
656      if (timeLimitMillis > 0 && System.currentTimeMillis() > stopTime)
657      {
658        resultCode = LDAPResultCode.TIME_LIMIT_EXCEEDED;
659
660        LocalizableMessage message = WARN_LDIFSEARCH_TIME_LIMIT_EXCEEDED.get();
661        err.println(message);
662        break;
663      }
664
665
666      try
667      {
668        Entry entry = reader.readEntry(checkSchema);
669        if (entry == null)
670        {
671          break;
672        }
673
674
675        // Check to see if the entry has an acceptable base and scope.
676        boolean matchesBaseAndScope = false;
677        for (DN baseDN : baseDNs)
678        {
679          if (entry.matchesBaseAndScope(baseDN, searchScope))
680          {
681            matchesBaseAndScope = true;
682            break;
683          }
684        }
685
686        if (! matchesBaseAndScope)
687        {
688          continue;
689        }
690
691
692        // Check to see if the entry matches any of the filters.
693        boolean matchesFilter = false;
694        for (SearchFilter filter : searchFilters)
695        {
696          if (filter.matchesEntry(entry))
697          {
698            matchesFilter = true;
699            break;
700          }
701        }
702
703        if (! matchesFilter)
704        {
705          continue;
706        }
707
708
709        // Prepare the entry to return to the client.
710        if (! allUserAttrs)
711        {
712          Iterator<AttributeType> iterator =
713               entry.getUserAttributes().keySet().iterator();
714          while (iterator.hasNext())
715          {
716            if (! userAttributeTypes.contains(iterator.next()))
717            {
718              iterator.remove();
719            }
720          }
721        }
722
723        if (! allOperationalAttrs)
724        {
725          Iterator<AttributeType> iterator =
726               entry.getOperationalAttributes().keySet().iterator();
727          while (iterator.hasNext())
728          {
729            if (! operationalAttributeTypes.contains(iterator.next()))
730            {
731              iterator.remove();
732            }
733          }
734        }
735
736
737        // Write the entry to the client and increase the count.
738        // FIXME -- Should we include a comment about which base+filter matched?
739        writer.writeEntry(entry);
740        writer.flush();
741
742        matchCount++;
743        if (sizeLimitValue > 0 && matchCount >= sizeLimitValue)
744        {
745          resultCode = LDAPResultCode.SIZE_LIMIT_EXCEEDED;
746
747          LocalizableMessage message = WARN_LDIFSEARCH_SIZE_LIMIT_EXCEEDED.get();
748          err.println(message);
749          break;
750        }
751      }
752      catch (LDIFException le)
753      {
754        if (le.canContinueReading())
755        {
756          LocalizableMessage message = ERR_LDIFSEARCH_CANNOT_READ_ENTRY_RECOVERABLE.get(
757                  le.getMessage());
758          err.println(message);
759        }
760        else
761        {
762          LocalizableMessage message = ERR_LDIFSEARCH_CANNOT_READ_ENTRY_FATAL.get(
763                  le.getMessage());
764          err.println(message);
765          resultCode = LDAPResultCode.CLIENT_SIDE_LOCAL_ERROR;
766          break;
767        }
768      }
769      catch (Exception e)
770      {
771        err.println(ERR_LDIFSEARCH_ERROR_DURING_PROCESSING.get(e));
772        resultCode = LDAPResultCode.CLIENT_SIDE_LOCAL_ERROR;
773        break;
774      }
775    }
776
777    close(reader, writer);
778
779    return resultCode;
780  }
781}
782