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 010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE 011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE. 012 * See the License for the specific language governing permissions 013 * and limitations under the License. 014 * 015 * When distributing Covered Code, include this CDDL HEADER in each 016 * file and include the License file at 017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, 018 * add the following below this CDDL HEADER, with the fields enclosed 019 * by brackets "[]" replaced with your own identifying information: 020 * Portions Copyright [yyyy] [name of copyright owner] 021 * 022 * CDDL HEADER END 023 * 024 * 025 * Copyright 2006-2008 Sun Microsystems, Inc. 026 */ 027 package org.opends.server.tools; 028 029 030 031 import java.io.File; 032 import java.io.OutputStream; 033 import java.io.PrintStream; 034 import java.util.ArrayList; 035 import java.util.HashSet; 036 import java.util.List; 037 import java.util.Random; 038 039 import org.opends.server.admin.std.server.BackendCfg; 040 import org.opends.server.api.Backend; 041 import org.opends.server.api.ErrorLogPublisher; 042 import org.opends.server.api.DebugLogPublisher; 043 import org.opends.server.api.plugin.PluginType; 044 import org.opends.server.config.ConfigException; 045 import static org.opends.server.config.ConfigConstants.*; 046 import org.opends.server.core.CoreConfigManager; 047 import org.opends.server.core.DirectoryServer; 048 import org.opends.server.core.LockFileManager; 049 import org.opends.server.extensions.ConfigFileHandler; 050 import org.opends.server.loggers.TextWriter; 051 import org.opends.server.loggers.TextErrorLogPublisher; 052 import org.opends.server.loggers.ErrorLogger; 053 import org.opends.server.loggers.debug.TextDebugLogPublisher; 054 import org.opends.server.loggers.debug.DebugLogger; 055 056 import org.opends.server.tools.makeldif.TemplateFile; 057 import org.opends.server.types.AttributeType; 058 import org.opends.server.types.DirectoryException; 059 import org.opends.server.types.DN; 060 import org.opends.server.types.ExistingFileBehavior; 061 import org.opends.server.types.InitializationException; 062 import org.opends.server.types.LDIFImportConfig; 063 import org.opends.server.types.LDIFImportResult; 064 import org.opends.server.types.NullOutputStream; 065 import org.opends.server.types.SearchFilter; 066 import org.opends.server.types.RawAttribute; 067 import org.opends.server.util.args.ArgumentException; 068 import org.opends.server.util.args.BooleanArgument; 069 import org.opends.server.util.args.IntegerArgument; 070 import org.opends.server.util.args.StringArgument; 071 import org.opends.server.util.args.LDAPConnectionArgumentParser; 072 073 import static org.opends.server.loggers.ErrorLogger.*; 074 import static org.opends.messages.ToolMessages.*; 075 import org.opends.messages.Message; 076 import static org.opends.server.util.ServerConstants.*; 077 import static org.opends.server.util.StaticUtils.*; 078 import static org.opends.server.tools.ToolConstants.*; 079 import org.opends.server.tools.tasks.TaskTool; 080 import org.opends.server.tasks.ImportTask; 081 import org.opends.server.protocols.asn1.ASN1OctetString; 082 import org.opends.server.protocols.ldap.LDAPAttribute; 083 084 085 /** 086 * This program provides a utility that may be used to import the contents of an 087 * LDIF file into a Directory Server backend. This will be a process that is 088 * intended to run separate from Directory Server and not internally within the 089 * server process (e.g., via the tasks interface). 090 */ 091 public class ImportLDIF extends TaskTool { 092 /** 093 * The buffer size that should be used when reading data from LDIF. 094 */ 095 public static final int LDIF_BUFFER_SIZE = 1048576; 096 097 098 /** 099 * The main method for ImportLDIF tool. 100 * 101 * @param args The command-line arguments provided to this program. 102 */ 103 public static void main(String[] args) 104 { 105 int retCode = mainImportLDIF(args, true, System.out, System.err); 106 107 if(retCode != 0) 108 { 109 System.exit(filterExitCode(retCode)); 110 } 111 } 112 113 /** 114 * Processes the command-line arguments and invokes the import process. 115 * 116 * @param args The command-line arguments provided to thisprogram. 117 * 118 * @return The error code. 119 */ 120 public static int mainImportLDIF(String[] args) 121 { 122 return mainImportLDIF(args, true, System.out, System.err); 123 } 124 125 /** 126 * Processes the command-line arguments and invokes the import process. 127 * 128 * @param args The command-line arguments provided to this 129 * program. 130 * @param initializeServer Indicates whether to initialize the server. 131 * @param outStream The output stream to use for standard output, or 132 * {@code null} if standard output is not needed. 133 * @param errStream The output stream to use for standard error, or 134 * {@code null} if standard error is not needed. 135 * 136 * @return The error code. 137 */ 138 public static int mainImportLDIF(String[] args, boolean initializeServer, 139 OutputStream outStream, 140 OutputStream errStream) 141 { 142 ImportLDIF tool = new ImportLDIF(); 143 return tool.process(args, initializeServer, outStream, errStream); 144 } 145 146 // Define the command-line arguments that may be used with this program. 147 private BooleanArgument append = null; 148 private BooleanArgument countRejects = null; 149 private BooleanArgument displayUsage = null; 150 private BooleanArgument isCompressed = null; 151 private BooleanArgument isEncrypted = null; 152 private BooleanArgument overwrite = null; 153 private BooleanArgument quietMode = null; 154 private BooleanArgument replaceExisting = null; 155 private BooleanArgument skipSchemaValidation = null; 156 private BooleanArgument clearBackend = null; 157 private IntegerArgument randomSeed = null; 158 private StringArgument backendID = null; 159 private StringArgument configClass = null; 160 private StringArgument configFile = null; 161 private StringArgument excludeAttributeStrings = null; 162 private StringArgument excludeBranchStrings = null; 163 private StringArgument excludeFilterStrings = null; 164 private StringArgument includeAttributeStrings = null; 165 private StringArgument includeBranchStrings = null; 166 private StringArgument includeFilterStrings = null; 167 private StringArgument ldifFiles = null; 168 private StringArgument rejectFile = null; 169 private StringArgument skipFile = null; 170 private StringArgument templateFile = null; 171 172 private int process(String[] args, boolean initializeServer, 173 OutputStream outStream, OutputStream errStream) { 174 175 PrintStream out; 176 if (outStream == null) 177 { 178 out = NullOutputStream.printStream(); 179 } 180 else 181 { 182 out = new PrintStream(outStream); 183 } 184 185 PrintStream err; 186 if (errStream == null) 187 { 188 err = NullOutputStream.printStream(); 189 } 190 else 191 { 192 err = new PrintStream(errStream); 193 } 194 195 // FIXME -- Need to add a mechanism for verifying the file signature. 196 197 198 // Create the command-line argument parser for use with this program. 199 LDAPConnectionArgumentParser argParser = 200 createArgParser("org.opends.server.tools.ImportLDIF", 201 INFO_LDIFIMPORT_TOOL_DESCRIPTION.get()); 202 203 // Initialize all the command-line argument types and register them with the 204 // parser. 205 try 206 { 207 configClass = 208 new StringArgument("configclass", OPTION_SHORT_CONFIG_CLASS, 209 OPTION_LONG_CONFIG_CLASS, true, false, 210 true, INFO_CONFIGCLASS_PLACEHOLDER.get(), 211 ConfigFileHandler.class.getName(), null, 212 INFO_DESCRIPTION_CONFIG_CLASS.get()); 213 configClass.setHidden(true); 214 argParser.addArgument(configClass); 215 216 217 configFile = 218 new StringArgument("configfile", 'f', "configFile", true, false, 219 true, INFO_CONFIGFILE_PLACEHOLDER.get(), null, 220 null, 221 INFO_DESCRIPTION_CONFIG_FILE.get()); 222 configFile.setHidden(true); 223 argParser.addArgument(configFile); 224 225 226 ldifFiles = 227 new StringArgument("ldiffile", OPTION_SHORT_LDIF_FILE, 228 OPTION_LONG_LDIF_FILE, false, true, true, 229 INFO_LDIFFILE_PLACEHOLDER.get(), null, null, 230 INFO_LDIFIMPORT_DESCRIPTION_LDIF_FILE.get()); 231 argParser.addArgument(ldifFiles); 232 233 234 templateFile = 235 new StringArgument("templatefile", 'A', "templateFile", false, false, 236 true, INFO_TEMPLATE_FILE_PLACEHOLDER.get(), null, 237 null, 238 INFO_LDIFIMPORT_DESCRIPTION_TEMPLATE_FILE.get()); 239 argParser.addArgument(templateFile); 240 241 242 append = 243 new BooleanArgument("append", 'a', "append", 244 INFO_LDIFIMPORT_DESCRIPTION_APPEND.get()); 245 argParser.addArgument(append); 246 247 248 replaceExisting = 249 new BooleanArgument( 250 "replaceexisting", 'r', "replaceExisting", 251 INFO_LDIFIMPORT_DESCRIPTION_REPLACE_EXISTING.get()); 252 argParser.addArgument(replaceExisting); 253 254 255 backendID = 256 new StringArgument("backendid", 'n', "backendID", false, false, true, 257 INFO_BACKENDNAME_PLACEHOLDER.get(), null, null, 258 INFO_LDIFIMPORT_DESCRIPTION_BACKEND_ID.get()); 259 argParser.addArgument(backendID); 260 261 clearBackend = 262 new BooleanArgument("clearbackend", 'F', "clearBackend", 263 INFO_LDIFIMPORT_DESCRIPTION_CLEAR_BACKEND.get()); 264 argParser.addArgument(clearBackend); 265 266 267 includeBranchStrings = 268 new StringArgument("includebranch", 'b', "includeBranch", false, 269 true, true, INFO_BRANCH_DN_PLACEHOLDER.get(), 270 null, null, 271 INFO_LDIFIMPORT_DESCRIPTION_INCLUDE_BRANCH.get()); 272 argParser.addArgument(includeBranchStrings); 273 274 275 excludeBranchStrings = 276 new StringArgument("excludebranch", 'B', "excludeBranch", false, 277 true, true, INFO_BRANCH_DN_PLACEHOLDER.get(), 278 null, null, 279 INFO_LDIFIMPORT_DESCRIPTION_EXCLUDE_BRANCH.get()); 280 argParser.addArgument(excludeBranchStrings); 281 282 283 includeAttributeStrings = 284 new StringArgument( 285 "includeattribute", 'i', "includeAttribute", 286 false, true, true, INFO_ATTRIBUTE_PLACEHOLDER.get(), null, 287 null, 288 INFO_LDIFIMPORT_DESCRIPTION_INCLUDE_ATTRIBUTE.get()); 289 argParser.addArgument(includeAttributeStrings); 290 291 292 excludeAttributeStrings = 293 new StringArgument( 294 "excludeattribute", 'e', "excludeAttribute", 295 false, true, true, INFO_ATTRIBUTE_PLACEHOLDER.get(), null, 296 null, 297 INFO_LDIFIMPORT_DESCRIPTION_EXCLUDE_ATTRIBUTE.get()); 298 argParser.addArgument(excludeAttributeStrings); 299 300 301 includeFilterStrings = 302 new StringArgument( 303 "includefilter", 'I', "includeFilter", 304 false, true, true, INFO_FILTER_PLACEHOLDER.get(), null, null, 305 INFO_LDIFIMPORT_DESCRIPTION_INCLUDE_FILTER.get()); 306 argParser.addArgument(includeFilterStrings); 307 308 309 excludeFilterStrings = 310 new StringArgument("excludefilter", 'E', "excludeFilter", 311 false, true, true, INFO_FILTER_PLACEHOLDER.get(), 312 null, null, 313 INFO_LDIFIMPORT_DESCRIPTION_EXCLUDE_FILTER.get()); 314 argParser.addArgument(excludeFilterStrings); 315 316 317 rejectFile = 318 new StringArgument("rejectfile", 'R', "rejectFile", false, false, 319 true, INFO_REJECT_FILE_PLACEHOLDER.get(), null, 320 null, 321 INFO_LDIFIMPORT_DESCRIPTION_REJECT_FILE.get()); 322 argParser.addArgument(rejectFile); 323 324 325 skipFile = 326 new StringArgument("skipfile", null, "skipFile", false, false, 327 true, INFO_SKIP_FILE_PLACEHOLDER.get(), null, 328 null, 329 INFO_LDIFIMPORT_DESCRIPTION_SKIP_FILE.get()); 330 argParser.addArgument(skipFile); 331 332 333 overwrite = 334 new BooleanArgument("overwrite", 'O', "overwrite", 335 INFO_LDIFIMPORT_DESCRIPTION_OVERWRITE.get()); 336 argParser.addArgument(overwrite); 337 338 339 randomSeed = 340 new IntegerArgument("randomseed", OPTION_SHORT_RANDOM_SEED, 341 OPTION_LONG_RANDOM_SEED, false, false, 342 true, INFO_SEED_PLACEHOLDER.get(), 343 0, null, false, 0, false, 0, 344 INFO_LDIFIMPORT_DESCRIPTION_RANDOM_SEED.get()); 345 argParser.addArgument(randomSeed); 346 347 348 skipSchemaValidation = 349 new BooleanArgument("skipschema", 'S', "skipSchemaValidation", 350 INFO_LDIFIMPORT_DESCRIPTION_SKIP_SCHEMA_VALIDATION.get()); 351 argParser.addArgument(skipSchemaValidation); 352 353 354 countRejects = 355 new BooleanArgument("countrejects", null, "countRejects", 356 INFO_LDIFIMPORT_DESCRIPTION_COUNT_REJECTS.get()); 357 argParser.addArgument(countRejects); 358 359 360 isCompressed = 361 new BooleanArgument("iscompressed", 'c', "isCompressed", 362 INFO_LDIFIMPORT_DESCRIPTION_IS_COMPRESSED.get()); 363 argParser.addArgument(isCompressed); 364 365 366 isEncrypted = 367 new BooleanArgument("isencrypted", 'y', "isEncrypted", 368 INFO_LDIFIMPORT_DESCRIPTION_IS_ENCRYPTED.get()); 369 isEncrypted.setHidden(true); //See issue #27 370 argParser.addArgument(isEncrypted); 371 372 373 quietMode = new BooleanArgument("quietmode", OPTION_SHORT_QUIET, 374 OPTION_LONG_QUIET, 375 INFO_LDIFIMPORT_DESCRIPTION_QUIET.get()); 376 argParser.addArgument(quietMode); 377 378 379 displayUsage = 380 new BooleanArgument("help", OPTION_SHORT_HELP, OPTION_LONG_HELP, 381 INFO_DESCRIPTION_USAGE.get()); 382 argParser.addArgument(displayUsage); 383 argParser.setUsageArgument(displayUsage); 384 } 385 catch (ArgumentException ae) 386 { 387 Message message = ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage()); 388 389 err.println(wrapText(message, MAX_LINE_WIDTH)); 390 return 1; 391 } 392 393 394 // Parse the command-line arguments provided to this program. 395 try 396 { 397 argParser.parseArguments(args); 398 validateTaskArgs(); 399 } 400 catch (ArgumentException ae) 401 { 402 Message message = ERR_ERROR_PARSING_ARGS.get(ae.getMessage()); 403 404 err.println(wrapText(message, MAX_LINE_WIDTH)); 405 err.println(argParser.getUsage()); 406 return 1; 407 } 408 409 410 // If we should just display usage or version information, 411 // then print it and exit. 412 if (argParser.usageOrVersionDisplayed()) 413 { 414 return 0; 415 } 416 417 418 // Make sure that either the "ldifFile" argument or the "templateFile" 419 // argument was provided, but not both. 420 if (ldifFiles.isPresent()) 421 { 422 if (templateFile.isPresent()) 423 { 424 Message message = ERR_LDIFIMPORT_CONFLICTING_OPTIONS.get( 425 ldifFiles.getLongIdentifier(), 426 templateFile.getLongIdentifier()); 427 err.println(wrapText(message, MAX_LINE_WIDTH)); 428 return 1; 429 } 430 } 431 else if (! templateFile.isPresent()) 432 { 433 Message message = ERR_LDIFIMPORT_MISSING_REQUIRED_ARGUMENT.get( 434 ldifFiles.getLongIdentifier(), 435 templateFile.getLongIdentifier()); 436 err.println(wrapText(message, MAX_LINE_WIDTH)); 437 return 1; 438 } 439 440 // Make sure that either the "includeBranchStrings" argument or the 441 // "backendID" argument was provided. 442 if(!includeBranchStrings.isPresent() && !backendID.isPresent()) 443 { 444 Message message = ERR_LDIFIMPORT_MISSING_BACKEND_ARGUMENT.get( 445 includeBranchStrings.getLongIdentifier(), 446 backendID.getLongIdentifier()); 447 err.println(wrapText(message, MAX_LINE_WIDTH)); 448 return 1; 449 } 450 451 // Don't write non-error messages to console if quite 452 if (quietMode.isPresent()) { 453 out = new PrintStream(NullOutputStream.instance()); 454 } 455 456 return process(argParser, initializeServer, out, err); 457 } 458 459 /** 460 * {@inheritDoc} 461 */ 462 public void addTaskAttributes(List<RawAttribute> attributes) 463 { 464 // 465 // Required attributes 466 // 467 ArrayList<ASN1OctetString> values; 468 List<String> fileList = ldifFiles.getValues(); 469 if (fileList != null && fileList.size() > 0) { 470 values = new ArrayList<ASN1OctetString>(fileList.size()); 471 for (String file : fileList) { 472 values.add(new ASN1OctetString(file)); 473 } 474 attributes.add(new LDAPAttribute(ATTR_IMPORT_LDIF_FILE, values)); 475 } 476 477 // 478 // Optional attributes 479 // 480 if (append.getValue() != null && 481 !append.getValue().equals(append.getDefaultValue())) { 482 values = new ArrayList<ASN1OctetString>(1); 483 values.add(new ASN1OctetString(append.getValue())); 484 attributes.add(new LDAPAttribute(ATTR_IMPORT_APPEND, values)); 485 } 486 487 if (replaceExisting.getValue() != null && 488 !replaceExisting.getValue().equals( 489 replaceExisting.getDefaultValue())) { 490 values = new ArrayList<ASN1OctetString>(1); 491 values.add(new ASN1OctetString(replaceExisting.getValue())); 492 attributes.add(new LDAPAttribute(ATTR_IMPORT_REPLACE_EXISTING, values)); 493 } 494 495 if (backendID.getValue() != null && 496 !backendID.getValue().equals( 497 backendID.getDefaultValue())) { 498 values = new ArrayList<ASN1OctetString>(1); 499 values.add(new ASN1OctetString(backendID.getValue())); 500 attributes.add(new LDAPAttribute(ATTR_IMPORT_BACKEND_ID, values)); 501 } 502 503 List<String> includeAttributes = includeAttributeStrings.getValues(); 504 if (includeAttributes != null && includeAttributes.size() > 0) { 505 values = new ArrayList<ASN1OctetString>(includeAttributes.size()); 506 for (String includeAttribute : includeAttributes) { 507 values.add(new ASN1OctetString(includeAttribute)); 508 } 509 attributes.add(new LDAPAttribute(ATTR_IMPORT_INCLUDE_ATTRIBUTE, values)); 510 } 511 512 List<String> excludeAttributes = excludeAttributeStrings.getValues(); 513 if (excludeAttributes != null && excludeAttributes.size() > 0) { 514 values = new ArrayList<ASN1OctetString>(excludeAttributes.size()); 515 for (String excludeAttribute : excludeAttributes) { 516 values.add(new ASN1OctetString(excludeAttribute)); 517 } 518 attributes.add(new LDAPAttribute(ATTR_IMPORT_EXCLUDE_ATTRIBUTE, values)); 519 } 520 521 List<String> includeFilters = includeFilterStrings.getValues(); 522 if (includeFilters != null && includeFilters.size() > 0) { 523 values = new ArrayList<ASN1OctetString>(includeFilters.size()); 524 for (String includeFilter : includeFilters) { 525 values.add(new ASN1OctetString(includeFilter)); 526 } 527 attributes.add(new LDAPAttribute(ATTR_IMPORT_INCLUDE_FILTER, values)); 528 } 529 530 List<String> excludeFilters = excludeFilterStrings.getValues(); 531 if (excludeFilters != null && excludeFilters.size() > 0) { 532 values = new ArrayList<ASN1OctetString>(excludeFilters.size()); 533 for (String excludeFilter : excludeFilters) { 534 values.add(new ASN1OctetString(excludeFilter)); 535 } 536 attributes.add(new LDAPAttribute(ATTR_IMPORT_EXCLUDE_FILTER, values)); 537 } 538 539 List<String> includeBranches = includeBranchStrings.getValues(); 540 if (includeBranches != null && includeBranches.size() > 0) { 541 values = new ArrayList<ASN1OctetString>(includeBranches.size()); 542 for (String includeBranche : includeBranches) { 543 values.add(new ASN1OctetString(includeBranche)); 544 } 545 attributes.add(new LDAPAttribute(ATTR_IMPORT_INCLUDE_BRANCH, values)); 546 } 547 548 List<String> excludeBranches = excludeBranchStrings.getValues(); 549 if (excludeBranches != null && excludeBranches.size() > 0) { 550 values = new ArrayList<ASN1OctetString>(excludeBranches.size()); 551 for (String excludeBranch : excludeBranches) { 552 values.add(new ASN1OctetString(excludeBranch)); 553 } 554 attributes.add(new LDAPAttribute(ATTR_IMPORT_EXCLUDE_BRANCH, values)); 555 } 556 557 if (rejectFile.getValue() != null && 558 !rejectFile.getValue().equals( 559 rejectFile.getDefaultValue())) { 560 values = new ArrayList<ASN1OctetString>(1); 561 values.add(new ASN1OctetString(rejectFile.getValue())); 562 attributes.add(new LDAPAttribute(ATTR_IMPORT_REJECT_FILE, values)); 563 } 564 565 if (skipFile.getValue() != null && 566 !skipFile.getValue().equals( 567 skipFile.getDefaultValue())) { 568 values = new ArrayList<ASN1OctetString>(1); 569 values.add(new ASN1OctetString(skipFile.getValue())); 570 attributes.add(new LDAPAttribute(ATTR_IMPORT_SKIP_FILE, values)); 571 } 572 573 if (overwrite.getValue() != null && 574 !overwrite.getValue().equals( 575 overwrite.getDefaultValue())) { 576 values = new ArrayList<ASN1OctetString>(1); 577 values.add(new ASN1OctetString(overwrite.getValue())); 578 attributes.add(new LDAPAttribute(ATTR_IMPORT_OVERWRITE, values)); 579 } 580 581 if (skipSchemaValidation.getValue() != null && 582 !skipSchemaValidation.getValue().equals( 583 skipSchemaValidation.getDefaultValue())) { 584 values = new ArrayList<ASN1OctetString>(1); 585 values.add(new ASN1OctetString(skipSchemaValidation.getValue())); 586 attributes.add( 587 new LDAPAttribute(ATTR_IMPORT_SKIP_SCHEMA_VALIDATION, values)); 588 } 589 590 if (isCompressed.getValue() != null && 591 !isCompressed.getValue().equals( 592 isCompressed.getDefaultValue())) { 593 values = new ArrayList<ASN1OctetString>(1); 594 values.add(new ASN1OctetString(isCompressed.getValue())); 595 attributes.add( 596 new LDAPAttribute(ATTR_IMPORT_IS_COMPRESSED, values)); 597 } 598 599 if (isEncrypted.getValue() != null && 600 !isEncrypted.getValue().equals( 601 isEncrypted.getDefaultValue())) { 602 values = new ArrayList<ASN1OctetString>(1); 603 values.add(new ASN1OctetString(isEncrypted.getValue())); 604 attributes.add( 605 new LDAPAttribute(ATTR_IMPORT_IS_ENCRYPTED, values)); 606 } 607 608 if (clearBackend.getValue() != null && 609 !clearBackend.getValue().equals( 610 clearBackend.getDefaultValue())) { 611 values = new ArrayList<ASN1OctetString>(1); 612 values.add(new ASN1OctetString(clearBackend.getValue())); 613 attributes.add( 614 new LDAPAttribute(ATTR_IMPORT_CLEAR_BACKEND, values)); 615 } 616 617 } 618 619 /** 620 * {@inheritDoc} 621 */ 622 public String getTaskObjectclass() { 623 return "ds-task-import"; 624 } 625 626 /** 627 * {@inheritDoc} 628 */ 629 public Class getTaskClass() { 630 return ImportTask.class; 631 } 632 633 /** 634 * {@inheritDoc} 635 */ 636 protected int processLocal(boolean initializeServer, 637 PrintStream out, 638 PrintStream err) { 639 640 641 // Perform the initial bootstrap of the Directory Server and process the 642 // configuration. 643 DirectoryServer directoryServer = DirectoryServer.getInstance(); 644 if (initializeServer) 645 { 646 try 647 { 648 DirectoryServer.bootstrapClient(); 649 DirectoryServer.initializeJMX(); 650 } 651 catch (Exception e) 652 { 653 Message message = ERR_SERVER_BOOTSTRAP_ERROR.get( 654 getExceptionMessage(e)); 655 err.println(wrapText(message, MAX_LINE_WIDTH)); 656 return 1; 657 } 658 659 try 660 { 661 directoryServer.initializeConfiguration(configClass.getValue(), 662 configFile.getValue()); 663 } 664 catch (InitializationException ie) 665 { 666 Message message = ERR_CANNOT_LOAD_CONFIG.get(ie.getMessage()); 667 err.println(wrapText(message, MAX_LINE_WIDTH)); 668 return 1; 669 } 670 catch (Exception e) 671 { 672 Message message = ERR_CANNOT_LOAD_CONFIG.get(getExceptionMessage(e)); 673 err.println(wrapText(message, MAX_LINE_WIDTH)); 674 return 1; 675 } 676 677 678 679 // Initialize the Directory Server schema elements. 680 try 681 { 682 directoryServer.initializeSchema(); 683 } 684 catch (ConfigException ce) 685 { 686 Message message = ERR_CANNOT_LOAD_SCHEMA.get(ce.getMessage()); 687 err.println(wrapText(message, MAX_LINE_WIDTH)); 688 return 1; 689 } 690 catch (InitializationException ie) 691 { 692 Message message = ERR_CANNOT_LOAD_SCHEMA.get(ie.getMessage()); 693 err.println(wrapText(message, MAX_LINE_WIDTH)); 694 return 1; 695 } 696 catch (Exception e) 697 { 698 Message message = ERR_CANNOT_LOAD_SCHEMA.get(getExceptionMessage(e)); 699 err.println(wrapText(message, MAX_LINE_WIDTH)); 700 return 1; 701 } 702 703 704 // Initialize the Directory Server core configuration. 705 try 706 { 707 CoreConfigManager coreConfigManager = new CoreConfigManager(); 708 coreConfigManager.initializeCoreConfig(); 709 } 710 catch (ConfigException ce) 711 { 712 Message message = ERR_CANNOT_INITIALIZE_CORE_CONFIG.get( 713 ce.getMessage()); 714 err.println(wrapText(message, MAX_LINE_WIDTH)); 715 return 1; 716 } 717 catch (InitializationException ie) 718 { 719 Message message = ERR_CANNOT_INITIALIZE_CORE_CONFIG.get( 720 ie.getMessage()); 721 err.println(wrapText(message, MAX_LINE_WIDTH)); 722 return 1; 723 } 724 catch (Exception e) 725 { 726 Message message = ERR_CANNOT_INITIALIZE_CORE_CONFIG.get( 727 getExceptionMessage(e)); 728 err.println(wrapText(message, MAX_LINE_WIDTH)); 729 return 1; 730 } 731 732 733 // Initialize the Directory Server crypto manager. 734 try 735 { 736 directoryServer.initializeCryptoManager(); 737 } 738 catch (ConfigException ce) 739 { 740 Message message = ERR_CANNOT_INITIALIZE_CRYPTO_MANAGER.get( 741 ce.getMessage()); 742 err.println(wrapText(message, MAX_LINE_WIDTH)); 743 return 1; 744 } 745 catch (InitializationException ie) 746 { 747 Message message = ERR_CANNOT_INITIALIZE_CRYPTO_MANAGER.get( 748 ie.getMessage()); 749 err.println(wrapText(message, MAX_LINE_WIDTH)); 750 return 1; 751 } 752 catch (Exception e) 753 { 754 Message message = ERR_CANNOT_INITIALIZE_CRYPTO_MANAGER.get( 755 getExceptionMessage(e)); 756 err.println(wrapText(message, MAX_LINE_WIDTH)); 757 return 1; 758 } 759 760 761 if (! quietMode.isPresent()) 762 { 763 try 764 { 765 ErrorLogPublisher errorLogPublisher = 766 TextErrorLogPublisher.getStartupTextErrorPublisher( 767 new TextWriter.STREAM(out)); 768 DebugLogPublisher debugLogPublisher = 769 TextDebugLogPublisher.getStartupTextDebugPublisher( 770 new TextWriter.STREAM(out)); 771 ErrorLogger.addErrorLogPublisher(errorLogPublisher); 772 DebugLogger.addDebugLogPublisher(debugLogPublisher); 773 } 774 catch(Exception e) 775 { 776 err.println("Error installing the custom error logger: " + 777 stackTraceToSingleLineString(e)); 778 } 779 } 780 781 782 // Initialize all the password policy information. 783 try 784 { 785 directoryServer.initializePasswordPolicyComponents(); 786 } 787 catch (ConfigException ce) 788 { 789 Message message = ERR_LDIFIMPORT_CANNOT_INITIALIZE_PWPOLICY.get( 790 ce.getMessage()); 791 err.println(wrapText(message, MAX_LINE_WIDTH)); 792 return 1; 793 } 794 catch (InitializationException ie) 795 { 796 Message message = ERR_LDIFIMPORT_CANNOT_INITIALIZE_PWPOLICY.get( 797 ie.getMessage()); 798 err.println(wrapText(message, MAX_LINE_WIDTH)); 799 return 1; 800 } 801 catch (Exception e) 802 { 803 Message message = ERR_LDIFIMPORT_CANNOT_INITIALIZE_PWPOLICY.get( 804 getExceptionMessage(e)); 805 err.println(wrapText(message, MAX_LINE_WIDTH)); 806 return 1; 807 } 808 809 810 // Make sure that the Directory Server plugin initialization is performed. 811 try 812 { 813 HashSet<PluginType> pluginTypes = new HashSet<PluginType>(1); 814 pluginTypes.add(PluginType.LDIF_IMPORT); 815 directoryServer.initializePlugins(pluginTypes); 816 } 817 catch (ConfigException ce) 818 { 819 Message message = ERR_LDIFIMPORT_CANNOT_INITIALIZE_PLUGINS.get( 820 ce.getMessage()); 821 err.println(wrapText(message, MAX_LINE_WIDTH)); 822 return 1; 823 } 824 catch (InitializationException ie) 825 { 826 Message message = ERR_LDIFIMPORT_CANNOT_INITIALIZE_PLUGINS.get( 827 ie.getMessage()); 828 err.println(wrapText(message, MAX_LINE_WIDTH)); 829 return 1; 830 } 831 catch (Exception e) 832 { 833 Message message = ERR_LDIFIMPORT_CANNOT_INITIALIZE_PLUGINS.get( 834 getExceptionMessage(e)); 835 err.println(wrapText(message, MAX_LINE_WIDTH)); 836 return 1; 837 } 838 } 839 840 841 // See if there were any user-defined sets of include/exclude attributes or 842 // filters. If so, then process them. 843 HashSet<AttributeType> excludeAttributes; 844 boolean excludeAllUserAttributes = false; 845 boolean excludeAllOperationalAttributes = false; 846 if (excludeAttributeStrings == null) 847 { 848 excludeAttributes = null; 849 } 850 else 851 { 852 excludeAttributes = new HashSet<AttributeType>(); 853 for (String attrName : excludeAttributeStrings.getValues()) 854 { 855 String lowerName = attrName.toLowerCase(); 856 if(lowerName.equals("*")) 857 { 858 excludeAllUserAttributes = true; 859 } 860 else if(lowerName.equals("+")) 861 { 862 excludeAllOperationalAttributes = true; 863 } 864 else 865 { 866 AttributeType attrType = DirectoryServer.getAttributeType(lowerName); 867 if (attrType == null) 868 { 869 attrType = DirectoryServer.getDefaultAttributeType(attrName); 870 } 871 872 excludeAttributes.add(attrType); 873 } 874 } 875 } 876 877 HashSet<AttributeType> includeAttributes; 878 boolean includeAllUserAttributes = false; 879 boolean includeAllOperationalAttributes = false; 880 if (includeAttributeStrings == null) 881 { 882 includeAttributes = null; 883 } 884 else 885 { 886 includeAttributes = new HashSet<AttributeType>(); 887 for (String attrName : includeAttributeStrings.getValues()) 888 { 889 String lowerName = attrName.toLowerCase(); 890 if(lowerName.equals("*")) 891 { 892 includeAllUserAttributes = true; 893 } 894 else if(lowerName.equals("+")) 895 { 896 includeAllOperationalAttributes = true; 897 } 898 else 899 { 900 AttributeType attrType = DirectoryServer.getAttributeType(lowerName); 901 if (attrType == null) 902 { 903 attrType = DirectoryServer.getDefaultAttributeType(attrName); 904 } 905 906 includeAttributes.add(attrType); 907 } 908 } 909 } 910 911 ArrayList<SearchFilter> excludeFilters; 912 if (excludeFilterStrings == null) 913 { 914 excludeFilters = null; 915 } 916 else 917 { 918 excludeFilters = new ArrayList<SearchFilter>(); 919 for (String filterString : excludeFilterStrings.getValues()) 920 { 921 try 922 { 923 excludeFilters.add(SearchFilter.createFilterFromString(filterString)); 924 } 925 catch (DirectoryException de) 926 { 927 Message message = ERR_LDIFIMPORT_CANNOT_PARSE_EXCLUDE_FILTER.get( 928 filterString, de.getMessageObject()); 929 logError(message); 930 return 1; 931 } 932 catch (Exception e) 933 { 934 Message message = ERR_LDIFIMPORT_CANNOT_PARSE_EXCLUDE_FILTER.get( 935 filterString, getExceptionMessage(e)); 936 logError(message); 937 return 1; 938 } 939 } 940 } 941 942 ArrayList<SearchFilter> includeFilters; 943 if (includeFilterStrings == null) 944 { 945 includeFilters = null; 946 } 947 else 948 { 949 includeFilters = new ArrayList<SearchFilter>(); 950 for (String filterString : includeFilterStrings.getValues()) 951 { 952 try 953 { 954 includeFilters.add(SearchFilter.createFilterFromString(filterString)); 955 } 956 catch (DirectoryException de) 957 { 958 Message message = ERR_LDIFIMPORT_CANNOT_PARSE_INCLUDE_FILTER.get( 959 filterString, de.getMessageObject()); 960 logError(message); 961 return 1; 962 } 963 catch (Exception e) 964 { 965 Message message = ERR_LDIFIMPORT_CANNOT_PARSE_INCLUDE_FILTER.get( 966 filterString, getExceptionMessage(e)); 967 logError(message); 968 return 1; 969 } 970 } 971 } 972 973 974 // Get information about the backends defined in the server. Iterate 975 // through them, finding the one backend into which the LDIF should be 976 // imported and finding backends with subordinate base DNs that should be 977 // excluded from the import. 978 Backend backend = null; 979 List<DN> defaultIncludeBranches = null; 980 List<DN> excludeBranches = new ArrayList<DN>(); 981 List<DN> includeBranches = new ArrayList<DN>(); 982 983 if (includeBranchStrings.isPresent()) 984 { 985 includeBranches = new ArrayList<DN>(); 986 for (String s : includeBranchStrings.getValues()) 987 { 988 DN includeBranch; 989 try 990 { 991 includeBranch = DN.decode(s); 992 } 993 catch (DirectoryException de) 994 { 995 Message message = ERR_LDIFIMPORT_CANNOT_DECODE_INCLUDE_BASE.get( 996 s, de.getMessageObject()); 997 logError(message); 998 return 1; 999 } 1000 catch (Exception e) 1001 { 1002 Message message = ERR_LDIFIMPORT_CANNOT_DECODE_INCLUDE_BASE.get( 1003 s, getExceptionMessage(e)); 1004 logError(message); 1005 return 1; 1006 } 1007 1008 includeBranches.add(includeBranch); 1009 } 1010 } 1011 1012 ArrayList<Backend> backendList = new ArrayList<Backend>(); 1013 ArrayList<BackendCfg> entryList = new ArrayList<BackendCfg>(); 1014 ArrayList<List<DN>> dnList = new ArrayList<List<DN>>(); 1015 int code = BackendToolUtils.getBackends(backendList, entryList, dnList); 1016 if (code != 0) 1017 { 1018 return code; 1019 } 1020 1021 int numBackends = backendList.size(); 1022 for (int i=0; i < numBackends; i++) 1023 { 1024 Backend b = backendList.get(i); 1025 1026 if(backendID.isPresent()) 1027 { 1028 if (! backendID.getValue().equals(b.getBackendID())) 1029 { 1030 continue; 1031 } 1032 } 1033 else 1034 { 1035 boolean useBackend = false; 1036 for(DN baseDN : dnList.get(i)) 1037 { 1038 for(DN includeDN : includeBranches) 1039 { 1040 if(baseDN.isAncestorOf(includeDN)) 1041 { 1042 useBackend = true; 1043 break; 1044 } 1045 } 1046 if(useBackend) 1047 { 1048 break; 1049 } 1050 } 1051 if(!useBackend) 1052 { 1053 continue; 1054 } 1055 } 1056 1057 if (backend == null) 1058 { 1059 backend = b; 1060 defaultIncludeBranches = dnList.get(i); 1061 } 1062 else 1063 { 1064 Message message = ERR_LDIFIMPORT_MULTIPLE_BACKENDS_FOR_ID.get(); 1065 logError(message); 1066 return 1; 1067 } 1068 } 1069 1070 if (backend == null) 1071 { 1072 Message message = 1073 ERR_LDIFIMPORT_NO_BACKENDS_FOR_ID.get(); 1074 logError(message); 1075 return 1; 1076 } 1077 else if (! backend.supportsLDIFImport()) 1078 { 1079 Message message = ERR_LDIFIMPORT_CANNOT_IMPORT.get(backendID.getValue()); 1080 logError(message); 1081 return 1; 1082 } 1083 1084 for (List<DN> baseList : dnList) 1085 { 1086 for (DN baseDN : baseList) 1087 { 1088 for (DN importBase : defaultIncludeBranches) 1089 { 1090 if (baseDN.isDescendantOf(importBase) && 1091 (! baseDN.equals(importBase))) 1092 { 1093 if (! excludeBranches.contains(baseDN)) 1094 { 1095 excludeBranches.add(baseDN); 1096 } 1097 1098 break; 1099 } 1100 } 1101 } 1102 } 1103 1104 // Make sure that if the "backendID" argument was provided, no include base 1105 // was included, and the "append" option was not provided, the 1106 // "clearBackend" argument was also provided if there are more then one 1107 // baseDNs for the backend being imported. 1108 if(backendID.isPresent() && !includeBranchStrings.isPresent() && 1109 !append.isPresent() && defaultIncludeBranches.size() > 1 && 1110 !clearBackend.isPresent()) 1111 { 1112 StringBuilder builder = new StringBuilder(); 1113 builder.append(backend.getBaseDNs()[0].toNormalizedString()); 1114 for(int i = 1; i < backend.getBaseDNs().length; i++) 1115 { 1116 builder.append(" / "); 1117 builder.append(backend.getBaseDNs()[i].toNormalizedString()); 1118 } 1119 Message message = ERR_LDIFIMPORT_MISSING_CLEAR_BACKEND.get( 1120 builder.toString(), clearBackend.getLongIdentifier()); 1121 err.println(wrapText(message, MAX_LINE_WIDTH)); 1122 return 1; 1123 } 1124 1125 for (String s : excludeBranchStrings.getValues()) 1126 { 1127 DN excludeBranch; 1128 try 1129 { 1130 excludeBranch = DN.decode(s); 1131 } 1132 catch (DirectoryException de) 1133 { 1134 Message message = ERR_LDIFIMPORT_CANNOT_DECODE_EXCLUDE_BASE.get( 1135 s, de.getMessageObject()); 1136 logError(message); 1137 return 1; 1138 } 1139 catch (Exception e) 1140 { 1141 Message message = ERR_LDIFIMPORT_CANNOT_DECODE_EXCLUDE_BASE.get( 1142 s, getExceptionMessage(e)); 1143 logError(message); 1144 return 1; 1145 } 1146 1147 if (! excludeBranches.contains(excludeBranch)) 1148 { 1149 excludeBranches.add(excludeBranch); 1150 } 1151 } 1152 1153 if (! includeBranchStrings.isPresent()) 1154 { 1155 includeBranches = defaultIncludeBranches; 1156 } 1157 else 1158 { 1159 // Make sure the selected backend will handle all the include branches 1160 for(DN includeBranch : includeBranches) 1161 { 1162 if (! Backend.handlesEntry(includeBranch, defaultIncludeBranches, 1163 excludeBranches)) 1164 { 1165 Message message = ERR_LDIFIMPORT_INVALID_INCLUDE_BASE.get( 1166 includeBranch.toNormalizedString(), backendID.getValue()); 1167 logError(message); 1168 return 1; 1169 } 1170 } 1171 } 1172 1173 1174 // See if the data should be read from LDIF files or generated via MakeLDIF. 1175 LDIFImportConfig importConfig; 1176 if (ldifFiles.isPresent()) 1177 { 1178 ArrayList<String> fileList = new ArrayList<String>(ldifFiles.getValues()); 1179 int badFileCount = 0; 1180 for (String pathname : fileList) 1181 { 1182 File f = new File(pathname); 1183 if (!f.canRead()) 1184 { 1185 Message message = ERR_LDIFIMPORT_CANNOT_READ_FILE.get(pathname); 1186 logError(message); 1187 badFileCount++; 1188 } 1189 } 1190 if (badFileCount > 0) return 1; 1191 importConfig = new LDIFImportConfig(fileList); 1192 } 1193 else 1194 { 1195 Random random; 1196 if (randomSeed.isPresent()) 1197 { 1198 try 1199 { 1200 random = new Random(randomSeed.getIntValue()); 1201 } 1202 catch (Exception e) 1203 { 1204 random = new Random(); 1205 } 1206 } 1207 else 1208 { 1209 random = new Random(); 1210 } 1211 1212 String resourcePath = DirectoryServer.getServerRoot() + File.separator + 1213 PATH_MAKELDIF_RESOURCE_DIR; 1214 TemplateFile tf = new TemplateFile(resourcePath, random); 1215 1216 ArrayList<Message> warnings = new ArrayList<Message>(); 1217 try 1218 { 1219 tf.parse(templateFile.getValue(), warnings); 1220 } 1221 catch (Exception e) 1222 { 1223 Message message = ERR_LDIFIMPORT_CANNOT_PARSE_TEMPLATE_FILE.get( 1224 templateFile.getValue(), e.getMessage()); 1225 logError(message); 1226 return 1; 1227 } 1228 1229 importConfig = new LDIFImportConfig(tf); 1230 } 1231 1232 1233 1234 // Create the LDIF import configuration to use when reading the LDIF. 1235 importConfig.setAppendToExistingData(append.isPresent()); 1236 importConfig.setReplaceExistingEntries(replaceExisting.isPresent()); 1237 importConfig.setCompressed(isCompressed.isPresent()); 1238 importConfig.setClearBackend(clearBackend.isPresent()); 1239 importConfig.setEncrypted(isEncrypted.isPresent()); 1240 importConfig.setExcludeAttributes(excludeAttributes); 1241 importConfig.setExcludeBranches(excludeBranches); 1242 importConfig.setExcludeFilters(excludeFilters); 1243 importConfig.setIncludeAttributes(includeAttributes); 1244 importConfig.setIncludeBranches(includeBranches); 1245 importConfig.setIncludeFilters(includeFilters); 1246 importConfig.setValidateSchema(!skipSchemaValidation.isPresent()); 1247 importConfig.setBufferSize(LDIF_BUFFER_SIZE); 1248 importConfig.setExcludeAllUserAttributes( 1249 excludeAllUserAttributes); 1250 importConfig.setExcludeAllOperationalAttributes( 1251 excludeAllOperationalAttributes); 1252 importConfig.setIncludeAllOpAttributes( 1253 includeAllOperationalAttributes); 1254 importConfig.setIncludeAllUserAttributes(includeAllUserAttributes); 1255 1256 // FIXME -- Should this be conditional? 1257 importConfig.setInvokeImportPlugins(true); 1258 1259 if (rejectFile != null) 1260 { 1261 try 1262 { 1263 ExistingFileBehavior existingBehavior; 1264 if (overwrite.isPresent()) 1265 { 1266 existingBehavior = ExistingFileBehavior.OVERWRITE; 1267 } 1268 else 1269 { 1270 existingBehavior = ExistingFileBehavior.APPEND; 1271 } 1272 1273 importConfig.writeRejectedEntries(rejectFile.getValue(), 1274 existingBehavior); 1275 } 1276 catch (Exception e) 1277 { 1278 Message message = ERR_LDIFIMPORT_CANNOT_OPEN_REJECTS_FILE.get( 1279 rejectFile.getValue(), getExceptionMessage(e)); 1280 logError(message); 1281 return 1; 1282 } 1283 } 1284 1285 if (skipFile != null) 1286 { 1287 try 1288 { 1289 ExistingFileBehavior existingBehavior; 1290 if (overwrite.isPresent()) 1291 { 1292 existingBehavior = ExistingFileBehavior.OVERWRITE; 1293 } 1294 else 1295 { 1296 existingBehavior = ExistingFileBehavior.APPEND; 1297 } 1298 1299 importConfig.writeSkippedEntries(skipFile.getValue(), 1300 existingBehavior); 1301 } 1302 catch (Exception e) 1303 { 1304 Message message = ERR_LDIFIMPORT_CANNOT_OPEN_SKIP_FILE.get( 1305 skipFile.getValue(), getExceptionMessage(e)); 1306 logError(message); 1307 return 1; 1308 } 1309 } 1310 1311 // Get the set of base DNs for the backend as an array. 1312 DN[] baseDNs = new DN[defaultIncludeBranches.size()]; 1313 defaultIncludeBranches.toArray(baseDNs); 1314 1315 1316 // Acquire an exclusive lock for the backend. 1317 try 1318 { 1319 String lockFile = LockFileManager.getBackendLockFileName(backend); 1320 StringBuilder failureReason = new StringBuilder(); 1321 if (! LockFileManager.acquireExclusiveLock(lockFile, failureReason)) 1322 { 1323 Message message = ERR_LDIFIMPORT_CANNOT_LOCK_BACKEND.get( 1324 backend.getBackendID(), String.valueOf(failureReason)); 1325 logError(message); 1326 return 1; 1327 } 1328 } 1329 catch (Exception e) 1330 { 1331 Message message = ERR_LDIFIMPORT_CANNOT_LOCK_BACKEND.get( 1332 backend.getBackendID(), getExceptionMessage(e)); 1333 logError(message); 1334 return 1; 1335 } 1336 1337 1338 // Launch the import. 1339 int retCode = 0; 1340 try 1341 { 1342 LDIFImportResult importResult = backend.importLDIF(importConfig); 1343 if (countRejects.isPresent()) 1344 { 1345 if (importResult.getEntriesRejected() > Integer.MAX_VALUE) 1346 { 1347 retCode = Integer.MAX_VALUE; 1348 } 1349 else 1350 { 1351 retCode = (int) importResult.getEntriesRejected(); 1352 } 1353 } 1354 } 1355 catch (DirectoryException de) 1356 { 1357 Message message = 1358 ERR_LDIFIMPORT_ERROR_DURING_IMPORT.get(de.getMessageObject()); 1359 logError(message); 1360 retCode = 1; 1361 } 1362 catch (Exception e) 1363 { 1364 Message message = 1365 ERR_LDIFIMPORT_ERROR_DURING_IMPORT.get(getExceptionMessage(e)); 1366 logError(message); 1367 retCode = 1; 1368 } 1369 1370 1371 // Release the exclusive lock on the backend. 1372 try 1373 { 1374 String lockFile = LockFileManager.getBackendLockFileName(backend); 1375 StringBuilder failureReason = new StringBuilder(); 1376 if (! LockFileManager.releaseLock(lockFile, failureReason)) 1377 { 1378 Message message = WARN_LDIFIMPORT_CANNOT_UNLOCK_BACKEND.get( 1379 backend.getBackendID(), String.valueOf(failureReason)); 1380 logError(message); 1381 retCode = 1; 1382 } 1383 } 1384 catch (Exception e) 1385 { 1386 Message message = WARN_LDIFIMPORT_CANNOT_UNLOCK_BACKEND.get( 1387 backend.getBackendID(), getExceptionMessage(e)); 1388 logError(message); 1389 retCode = 1; 1390 } 1391 1392 1393 // Clean up after the import by closing the import config. 1394 importConfig.close(); 1395 return retCode; 1396 } 1397 } 1398