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 * Portions Copyright 2013-2016 ForgeRock AS. 015 */ 016package org.opends.server.tools.upgrade; 017 018import static java.nio.charset.StandardCharsets.*; 019import static java.nio.file.StandardOpenOption.*; 020import static javax.security.auth.callback.ConfirmationCallback.NO; 021import static javax.security.auth.callback.ConfirmationCallback.YES; 022import static javax.security.auth.callback.TextOutputCallback.*; 023 024import static org.forgerock.util.Utils.joinAsString; 025import static org.opends.messages.ToolMessages.*; 026import static org.opends.server.tools.upgrade.FileManager.copyRecursively; 027import static org.opends.server.tools.upgrade.UpgradeUtils.*; 028import static org.opends.server.types.Schema.*; 029import static org.opends.server.util.StaticUtils.*; 030 031import java.io.BufferedWriter; 032import java.io.File; 033import java.io.FileReader; 034import java.io.IOException; 035import java.nio.file.Files; 036import java.util.ArrayList; 037import java.util.Arrays; 038import java.util.Collections; 039import java.util.HashSet; 040import java.util.LinkedHashSet; 041import java.util.LinkedList; 042import java.util.List; 043import java.util.Map; 044import java.util.Set; 045import java.util.TreeMap; 046import java.util.TreeSet; 047 048import javax.security.auth.callback.TextOutputCallback; 049 050import org.forgerock.i18n.LocalizableMessage; 051import org.forgerock.i18n.slf4j.LocalizedLogger; 052import org.forgerock.opendj.ldap.DN; 053import org.forgerock.opendj.ldap.Entry; 054import org.forgerock.opendj.ldap.Filter; 055import org.forgerock.opendj.ldap.SearchScope; 056import org.forgerock.opendj.ldap.requests.Requests; 057import org.forgerock.opendj.ldap.requests.SearchRequest; 058import org.forgerock.opendj.ldap.schema.AttributeType; 059import org.forgerock.opendj.ldap.schema.CoreSchema; 060import org.forgerock.opendj.ldap.schema.MatchingRule; 061import org.forgerock.opendj.ldap.schema.Schema; 062import org.forgerock.opendj.ldap.schema.SchemaBuilder; 063import org.forgerock.opendj.ldap.schema.Syntax; 064import org.forgerock.opendj.ldif.EntryReader; 065import org.forgerock.opendj.ldif.LDIFEntryReader; 066import org.opends.server.backends.pluggable.spi.TreeName; 067import org.opends.server.tools.JavaPropertiesTool; 068import org.opends.server.tools.RebuildIndex; 069import org.opends.server.util.BuildVersion; 070import org.opends.server.util.ChangeOperationType; 071import org.opends.server.util.StaticUtils; 072 073import com.forgerock.opendj.cli.ClientException; 074import com.forgerock.opendj.cli.ReturnCode; 075import com.sleepycat.je.DatabaseException; 076import com.sleepycat.je.Environment; 077import com.sleepycat.je.EnvironmentConfig; 078import com.sleepycat.je.Transaction; 079import com.sleepycat.je.TransactionConfig; 080 081/** Factory methods for create new upgrade tasks. */ 082public final class UpgradeTasks 083{ 084 /** Logger for the upgrade. */ 085 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 086 087 /** An errors counter in case of ignore errors mode. */ 088 static int countErrors; 089 090 /** Contains all the indexes to rebuild. */ 091 private static final Set<String> indexesToRebuild = new LinkedHashSet<>(); 092 093 /** A flag to avoid rebuild single indexes if 'rebuild all' is selected. */ 094 private static boolean isRebuildAllIndexesIsPresent; 095 /** A flag for marking 'rebuild all' task accepted by user. */ 096 private static boolean isRebuildAllIndexesTaskAccepted; 097 098 private static final List<String> SUPPORTED_LOCALES_FOR_3_0_0 = Arrays.asList( 099 "ca_ES", "de", "es", "fr", "ja", "ko", "pl", "zh_CN", "zh_TW"); 100 101 /** 102 * Returns a new upgrade task which adds a config entry to the underlying 103 * config file. 104 * 105 * @param summary 106 * The summary of this upgrade task. 107 * @param ldif 108 * The LDIF record which will be applied to matching entries. 109 * @return A new upgrade task which applies an LDIF record to all 110 * configuration entries matching the provided filter. 111 */ 112 public static UpgradeTask addConfigEntry(final LocalizableMessage summary, 113 final String... ldif) 114 { 115 return updateConfigEntry(summary, null, ChangeOperationType.ADD, ldif); 116 } 117 118 /** 119 * Returns a new upgrade task which adds a config entry to the underlying 120 * config file. No summary message will be output. 121 * 122 * @param ldif 123 * The LDIF record which will be applied to matching entries. 124 * @return A new upgrade task which applies an LDIF record to all 125 * configuration entries matching the provided filter. 126 */ 127 public static UpgradeTask addConfigEntry(final String... ldif) 128 { 129 return new AbstractUpgradeTask() 130 { 131 @Override 132 public void perform(final UpgradeContext context) throws ClientException 133 { 134 try 135 { 136 final int changeCount = updateConfigFile(configFile, null, ChangeOperationType.ADD, ldif); 137 displayChangeCount(configFile, changeCount); 138 } 139 catch (final Exception e) 140 { 141 countErrors++; 142 throw new ClientException(ReturnCode.ERROR_UNEXPECTED, LocalizableMessage.raw(e.getMessage())); 143 } 144 } 145 146 @Override 147 public String toString() 148 { 149 return "Add entry " + ldif[0]; 150 } 151 }; 152 } 153 154 /** 155 * This task copies the file placed in parameter within the config / schema 156 * folder. If the file already exists, it's overwritten. 157 * 158 * @param fileName 159 * The name of the file which need to be copied. 160 * @return A task which copy the the file placed in parameter within the 161 * config / schema folder. If the file already exists, it's 162 * overwritten. 163 */ 164 public static UpgradeTask copySchemaFile(final String fileName) 165 { 166 return new AbstractUpgradeTask() 167 { 168 @Override 169 public void perform(final UpgradeContext context) throws ClientException 170 { 171 final LocalizableMessage msg = INFO_UPGRADE_TASK_REPLACE_SCHEMA_FILE.get(fileName); 172 logger.debug(msg); 173 174 final ProgressNotificationCallback pnc = new ProgressNotificationCallback(INFORMATION, msg, 0); 175 176 final File schemaFileTemplate = 177 new File(templateConfigSchemaDirectory, fileName); 178 179 try 180 { 181 context.notifyProgress(pnc.setProgress(20)); 182 if (!schemaFileTemplate.exists() || schemaFileTemplate.length() == 0) 183 { 184 throw new IOException(ERR_UPGRADE_CORRUPTED_TEMPLATE 185 .get(schemaFileTemplate.getPath()).toString()); 186 } 187 copyRecursively(schemaFileTemplate, configSchemaDirectory, true); 188 context.notifyProgress(pnc.setProgress(100)); 189 } 190 catch (final IOException e) 191 { 192 throw unexpectedException(context, pnc, ERR_UPGRADE_COPYSCHEMA_FAILS.get( 193 schemaFileTemplate.getName(), e.getMessage())); 194 } 195 } 196 197 @Override 198 public String toString() 199 { 200 return INFO_UPGRADE_TASK_REPLACE_SCHEMA_FILE.get(fileName).toString(); 201 } 202 }; 203 } 204 205 /** 206 * This task copies the file placed in parameter within the config folder. If 207 * the file already exists, it's overwritten. 208 * 209 * @param fileName 210 * The name of the file which need to be copied. 211 * @return A task which copy the the file placed in parameter within the 212 * config folder. If the file already exists, it's overwritten. 213 */ 214 public static UpgradeTask addConfigFile(final String fileName) 215 { 216 return new AbstractUpgradeTask() 217 { 218 @Override 219 public void perform(final UpgradeContext context) throws ClientException 220 { 221 final LocalizableMessage msg = INFO_UPGRADE_TASK_ADD_CONFIG_FILE.get(fileName); 222 logger.debug(msg); 223 224 final ProgressNotificationCallback pnc = new ProgressNotificationCallback(INFORMATION, msg, 0); 225 226 final File configFile = new File(templateConfigDirectory, fileName); 227 228 try 229 { 230 context.notifyProgress(pnc.setProgress(20)); 231 232 copyRecursively(configFile, configDirectory, true); 233 context.notifyProgress(pnc.setProgress(100)); 234 } 235 catch (final IOException e) 236 { 237 throw unexpectedException(context, pnc, ERR_UPGRADE_ADD_CONFIG_FILE_FAILS.get( 238 configFile.getName(), e.getMessage())); 239 } 240 } 241 242 @Override 243 public String toString() 244 { 245 return INFO_UPGRADE_TASK_ADD_CONFIG_FILE.get(fileName).toString(); 246 } 247 }; 248 } 249 250 /** 251 * Returns a new upgrade task which deletes a config entry from the underlying config file. 252 * 253 * @param summary 254 * The summary of this upgrade task. 255 * @param dnsInLDIF 256 * The dns to delete in the form of LDIF. 257 * @return A new upgrade task which applies an LDIF record to all configuration entries matching 258 * the provided filter. 259 */ 260 public static UpgradeTask deleteConfigEntry(final LocalizableMessage summary, final String... dnsInLDIF) 261 { 262 return updateConfigEntry(summary, null, ChangeOperationType.DELETE, dnsInLDIF); 263 } 264 265 /** 266 * Returns a new upgrade task which applies an LDIF record to all 267 * configuration entries matching the provided filter. 268 * 269 * @param summary 270 * The summary of this upgrade task. 271 * @param filter 272 * The LDAP filter which configuration entries must match. 273 * @param ldif 274 * The LDIF record which will be applied to matching entries. 275 * @return A new upgrade task which applies an LDIF record to all 276 * configuration entries matching the provided filter. 277 */ 278 public static UpgradeTask modifyConfigEntry(final LocalizableMessage summary, 279 final String filter, final String... ldif) 280 { 281 return updateConfigEntry(summary, filter, ChangeOperationType.MODIFY, ldif); 282 } 283 284 /** 285 * This task adds or updates an attribute type (must exist in the original file) 286 * to the file specified in {@code fileName}. The destination must be a file 287 * contained in the config/schema folder. The attribute type is updated if an 288 * attribute with the same OID exists. 289 * 290 * e.g : This example adds a new attribute type named 'etag' in the 00-core.ldif. 291 * The 'etag' attribute already exists in the 00-core.ldif template schema file. 292 * 293 * <pre> 294 * register("2.5.0", 295 * newAttributeTypes(LocalizableMessage.raw("New attribute etag"), 296 * false, "00-core.ldif", 297 * "1.3.6.1.4.1.36733.2.1.1.59")); 298 * </pre> 299 * 300 * @param summary 301 * The summary of the task. 302 * @param fileName 303 * The file where to add the new definitions. This file must be 304 * contained in the configuration/schema folder. 305 * @param attributeOids 306 * The OIDs of the attributes to add or update. 307 * @return An upgrade task which adds or updates attribute types, defined 308 * previously in the configuration template files, reads the 309 * definition and adds it onto the file specified in {@code fileName} 310 */ 311 public static UpgradeTask newAttributeTypes(final LocalizableMessage summary, 312 final String fileName, final String... attributeOids) 313 { 314 return new AbstractUpgradeTask() 315 { 316 @Override 317 public void perform(final UpgradeContext context) throws ClientException 318 { 319 logger.debug(summary); 320 321 final ProgressNotificationCallback pnc = new ProgressNotificationCallback(INFORMATION, summary, 20); 322 context.notifyProgress(pnc); 323 324 final File schemaFileTemplate = new File(templateConfigSchemaDirectory, fileName); 325 final File pathDestination = new File(configSchemaDirectory, fileName); 326 try 327 { 328 final int changeCount = updateSchemaFile(schemaFileTemplate, pathDestination, attributeOids, null); 329 displayChangeCount(pathDestination, changeCount); 330 context.notifyProgress(pnc.setProgress(100)); 331 } 332 catch (final IOException | IllegalStateException e) 333 { 334 throw unexpectedException(context, pnc, ERR_UPGRADE_ADDATTRIBUTE_FAILS.get( 335 schemaFileTemplate.getName(), e.getMessage())); 336 } 337 } 338 339 @Override 340 public String toString() 341 { 342 return String.valueOf(summary); 343 } 344 }; 345 } 346 347 /** 348 * This task adds or updates an object class (must exist in the original file) 349 * to the file specified in {@code fileName}. The destination must be a file 350 * contained in the config/schema folder. The object class will be updated if 351 * a definition with the same OID exists, and added otherwise. 352 * 353 * @param summary 354 * The summary of the task. 355 * @param fileName 356 * The file where to add the new definitions. This file must be 357 * contained in the configuration/schema folder. 358 * @param objectClassesOids 359 * The OIDs of the object classes to add or update. 360 * @return An upgrade task which adds or updates object classes, defined 361 * previously in the configuration template files, reads the 362 * definition and adds it onto the file specified in {@code fileName} 363 */ 364 public static UpgradeTask newObjectClasses(final LocalizableMessage summary, 365 final String fileName, final String... objectClassesOids) 366 { 367 return new AbstractUpgradeTask() 368 { 369 @Override 370 public void perform(final UpgradeContext context) throws ClientException 371 { 372 logger.debug(summary); 373 374 final ProgressNotificationCallback pnc = new ProgressNotificationCallback(INFORMATION, summary, 20); 375 context.notifyProgress(pnc); 376 377 final File schemaFileTemplate = new File(templateConfigSchemaDirectory, fileName); 378 final File pathDestination = new File(configSchemaDirectory, fileName); 379 context.notifyProgress(pnc.setProgress(20)); 380 381 try 382 { 383 final int changeCount = updateSchemaFile(schemaFileTemplate, pathDestination, null, objectClassesOids); 384 displayChangeCount(pathDestination, changeCount); 385 context.notifyProgress(pnc.setProgress(100)); 386 } 387 catch (final IOException e) 388 { 389 throw unexpectedException(context, pnc, ERR_UPGRADE_ADDOBJECTCLASS_FAILS.get( 390 schemaFileTemplate.getName(), e.getMessage())); 391 } 392 catch (final IllegalStateException e) 393 { 394 throw unexpectedException(context, pnc, ERR_UPGRADE_ADDATTRIBUTE_FAILS.get( 395 schemaFileTemplate.getName(), e.getMessage())); 396 } 397 } 398 399 @Override 400 public String toString() 401 { 402 return String.valueOf(summary); 403 } 404 }; 405 } 406 407 /** 408 * Re-run the dsjavaproperties tool to rewrite the set-java-home script/batch file. 409 * 410 * @param summary 411 * The summary of the task. 412 * @return An upgrade task which runs dsjavaproperties. 413 */ 414 public static UpgradeTask rerunJavaPropertiesTool(final LocalizableMessage summary) 415 { 416 return new AbstractUpgradeTask() 417 { 418 @Override 419 public void perform(UpgradeContext context) throws ClientException 420 { 421 logger.debug(summary); 422 423 final ProgressNotificationCallback pnc = new ProgressNotificationCallback(INFORMATION, summary, 50); 424 context.notifyProgress(pnc); 425 426 int returnValue = JavaPropertiesTool.mainCLI("--quiet"); 427 context.notifyProgress(pnc.setProgress(100)); 428 429 if (JavaPropertiesTool.ErrorReturnCode.SUCCESSFUL.getReturnCode() != returnValue && 430 JavaPropertiesTool.ErrorReturnCode.SUCCESSFUL_NOP.getReturnCode() != returnValue) { 431 throw new ClientException(ReturnCode.ERROR_UNEXPECTED, ERR_UPGRADE_DSJAVAPROPERTIES_FAILED.get()); 432 } 433 } 434 435 @Override 436 public String toString() 437 { 438 return String.valueOf(summary); 439 } 440 }; 441 } 442 443 /** 444 * Creates a group of tasks which will only be invoked if the current version 445 * is more recent than the provided version. This may be useful in cases where 446 * a regression was introduced in version X and resolved in a later version Y. 447 * In this case, the provided upgrade tasks will only be invoked if the 448 * current version is between X (inclusive) and Y (exclusive). 449 * 450 * @param versionString 451 * The lower bound version. The upgrade tasks will not be applied if 452 * the current version is older than this version. 453 * @param tasks 454 * The group of tasks to invoke if the current version is equal to or 455 * more recent than {@code versionString}. 456 * @return An upgrade task which will only be invoked if the current version 457 * is more recent than the provided version. 458 */ 459 public static UpgradeTask regressionInVersion(final String versionString, final UpgradeTask... tasks) 460 { 461 final BuildVersion version = BuildVersion.valueOf(versionString); 462 return conditionalUpgradeTasks(new UpgradeCondition() 463 { 464 @Override 465 public boolean shouldPerformUpgradeTasks(final UpgradeContext context) throws ClientException 466 { 467 return context.getFromVersion().compareTo(version) >= 0; 468 } 469 470 @Override 471 public String toString() 472 { 473 return "Regression in version \"" + versionString + "\""; 474 } 475 }, tasks); 476 } 477 478 /** 479 * Creates a group of tasks which will only be invoked if the user confirms agreement. This may be 480 * useful in cases where a feature is deprecated and the upgrade is capable of migrating the 481 * configuration to the new replacement feature. 482 * 483 * @param message 484 * The confirmation message. 485 * @param tasks 486 * The group of tasks to invoke if the user agrees. 487 * @return An upgrade task which will only be invoked if the user confirms agreement. 488 */ 489 static UpgradeTask requireConfirmation( 490 final LocalizableMessage message, final int defaultResponse, final UpgradeTask... tasks) 491 { 492 return conditionalUpgradeTasks(new UpgradeCondition() 493 { 494 @Override 495 public boolean shouldPerformUpgradeTasks(final UpgradeContext context) throws ClientException 496 { 497 return context.confirmYN(INFO_UPGRADE_TASK_NEEDS_USER_CONFIRM.get(message), defaultResponse) == YES; 498 } 499 500 @Override 501 public String toString() 502 { 503 return INFO_UPGRADE_TASK_NEEDS_USER_CONFIRM.get(message).toString(); 504 } 505 }, tasks); 506 } 507 508 /** Determines whether conditional tasks should be performed. */ 509 interface UpgradeCondition 510 { 511 boolean shouldPerformUpgradeTasks(UpgradeContext context) throws ClientException; 512 } 513 514 static UpgradeTask conditionalUpgradeTasks(final UpgradeCondition condition, final UpgradeTask... tasks) 515 { 516 return new AbstractUpgradeTask() 517 { 518 private boolean shouldPerformUpgradeTasks = true; 519 520 @Override 521 public void prepare(final UpgradeContext context) throws ClientException 522 { 523 shouldPerformUpgradeTasks = condition.shouldPerformUpgradeTasks(context); 524 if (shouldPerformUpgradeTasks) 525 { 526 for (UpgradeTask task : tasks) 527 { 528 task.prepare(context); 529 } 530 } 531 } 532 533 @Override 534 public void perform(final UpgradeContext context) throws ClientException 535 { 536 if (shouldPerformUpgradeTasks) 537 { 538 for (UpgradeTask task : tasks) 539 { 540 try 541 { 542 task.perform(context); 543 } 544 catch (ClientException e) 545 { 546 handleClientException(context, e); 547 } 548 } 549 } 550 } 551 552 @Override 553 public void postUpgrade(UpgradeContext context) throws ClientException 554 { 555 if (shouldPerformUpgradeTasks) 556 { 557 boolean isOk = true; 558 for (final UpgradeTask task : tasks) 559 { 560 if (isOk) 561 { 562 try 563 { 564 task.postUpgrade(context); 565 } 566 catch (ClientException e) 567 { 568 logger.error(e.getMessageObject()); 569 isOk = false; 570 } 571 } 572 else 573 { 574 task.postponePostUpgrade(context); 575 } 576 } 577 } 578 } 579 580 @Override 581 public String toString() 582 { 583 final StringBuilder sb = new StringBuilder(); 584 sb.append(condition).append(" = ").append(shouldPerformUpgradeTasks).append('\n'); 585 sb.append('['); 586 joinAsString(sb, "\n", (Object[]) tasks); 587 sb.append(']'); 588 return sb.toString(); 589 } 590 }; 591 } 592 593 /** 594 * Creates a rebuild all indexes task. 595 * 596 * @param summary 597 * The summary of this upgrade task. 598 * @return An Upgrade task which rebuild all the indexes. 599 */ 600 public static UpgradeTask rebuildAllIndexes(final LocalizableMessage summary) 601 { 602 return new AbstractUpgradeTask() 603 { 604 private boolean isATaskToPerform; 605 606 @Override 607 public void prepare(UpgradeContext context) throws ClientException 608 { 609 Upgrade.needToRunPostUpgradePhase(); 610 // Requires answer from the user. 611 isATaskToPerform = context.confirmYN(summary, NO) == YES; 612 isRebuildAllIndexesIsPresent = true; 613 isRebuildAllIndexesTaskAccepted = isATaskToPerform; 614 } 615 616 @Override 617 public void postUpgrade(final UpgradeContext context) throws ClientException 618 { 619 if (!isATaskToPerform) 620 { 621 postponePostUpgrade(context); 622 } 623 } 624 625 @Override 626 public void postponePostUpgrade(UpgradeContext context) throws ClientException 627 { 628 context.notify(INFO_UPGRADE_ALL_REBUILD_INDEX_DECLINED.get(), TextOutputCallback.WARNING); 629 } 630 631 @Override 632 public String toString() 633 { 634 return String.valueOf(summary); 635 } 636 }; 637 } 638 639 /** 640 * Creates a rebuild index task for a given single index. As this task is 641 * possibly lengthy, it's considered as a post upgrade task. This task is not 642 * mandatory; e.g not require user interaction, but could be required to get a 643 * fully functional server. <br /> 644 * The post upgrade task just register the task. The rebuild indexes tasks are 645 * completed at the end of the upgrade process. 646 * 647 * @param summary 648 * A message describing why the index needs to be rebuilt and asking 649 * them whether they wish to perform this task after the upgrade. 650 * @param indexNames 651 * The indexes to rebuild. 652 * @return The rebuild index task. 653 */ 654 public static UpgradeTask rebuildIndexesNamed(final LocalizableMessage summary, final String... indexNames) 655 { 656 return new AbstractUpgradeTask() 657 { 658 private boolean isATaskToPerform; 659 660 @Override 661 public void prepare(UpgradeContext context) throws ClientException 662 { 663 Upgrade.needToRunPostUpgradePhase(); 664 // Requires answer from the user. 665 isATaskToPerform = context.confirmYN(summary, NO) == YES; 666 } 667 668 @Override 669 public void postUpgrade(final UpgradeContext context) throws ClientException 670 { 671 if (isATaskToPerform) 672 { 673 Collections.addAll(indexesToRebuild, indexNames); 674 } 675 else 676 { 677 postponePostUpgrade(context); 678 } 679 } 680 681 @Override 682 public void postponePostUpgrade(UpgradeContext context) throws ClientException 683 { 684 if (!isRebuildAllIndexesIsPresent) 685 { 686 context.notify(INFO_UPGRADE_REBUILD_INDEXES_DECLINED.get(joinAsString(", ", indexNames)), 687 TextOutputCallback.WARNING); 688 } 689 } 690 691 @Override 692 public String toString() 693 { 694 return String.valueOf(summary); 695 } 696 }; 697 } 698 699 /** 700 * This task is processed at the end of the upgrade, rebuilding indexes. If a 701 * rebuild all indexes has been registered before, it takes the flag 702 * relatively to single rebuild index. 703 * 704 * @return The post upgrade rebuild indexes task. 705 */ 706 public static UpgradeTask postUpgradeRebuildIndexes() 707 { 708 return new AbstractUpgradeTask() 709 { 710 @Override 711 public void postUpgrade(final UpgradeContext context) throws ClientException 712 { 713 if (!isRebuildAllIndexesIsPresent && indexesToRebuild.isEmpty()) 714 { 715 return; 716 } 717 718 final Map<String, Set<String>> baseDNsForBackends = UpgradeUtils.getBaseDNsPerBackendsFromConfig(); 719 if (isRebuildAllIndexesTaskAccepted) 720 { 721 final Set<String> allBaseDNs = new HashSet<>(); 722 for (final Set<String> baseDNsForBackend : baseDNsForBackends.values()) 723 { 724 allBaseDNs.addAll(baseDNsForBackend); 725 } 726 rebuildIndex(INFO_UPGRADE_REBUILD_ALL.get(), context, allBaseDNs, Collections.singletonList("--rebuildAll")); 727 } 728 else 729 { 730 for (final Map.Entry<String, Set<String>> backendEntry : baseDNsForBackends.entrySet()) 731 { 732 final String backend = backendEntry.getKey(); 733 if (indexesToRebuild.isEmpty()) 734 { 735 logger.debug(INFO_UPGRADE_NO_INDEX_TO_REBUILD_FOR_BACKEND.get(backend)); 736 continue; 737 } 738 739 final List<String> args = new ArrayList<>(); 740 for (final String indexToRebuild : indexesToRebuild) 741 { 742 args.add("--index"); 743 args.add(indexToRebuild); 744 } 745 final Set<String> baseDNs = backendEntry.getValue(); 746 rebuildIndex(INFO_UPGRADE_REBUILD_INDEX_STARTS.get(indexesToRebuild, baseDNs), context, baseDNs, args); 747 } 748 } 749 } 750 751 private void rebuildIndex(final LocalizableMessage infoMsg, final UpgradeContext context, 752 final Set<String> baseDNs, final List<String> baseArgs) throws ClientException 753 { 754 final ProgressNotificationCallback pnc = new ProgressNotificationCallback(INFORMATION, infoMsg, 25); 755 logger.debug(infoMsg); 756 context.notifyProgress(pnc); 757 758 List<String> args = new ArrayList<>(baseArgs); 759 args.add("--configFile"); 760 args.add(configFile.getAbsolutePath()); 761 for (final String be : baseDNs) 762 { 763 args.add("--baseDN"); 764 args.add(be); 765 } 766 logger.debug(INFO_UPGRADE_REBUILD_INDEX_ARGUMENTS, args); 767 768 final int result = new RebuildIndex().rebuildIndexesWithinMultipleBackends( 769 true, UpgradeLog.getPrintStream(), args); 770 if (result != 0) 771 { 772 final LocalizableMessage msg = ERR_UPGRADE_PERFORMING_POST_TASKS_FAIL.get(); 773 context.notifyProgress(pnc.setProgress(-100)); 774 throw new ClientException(ReturnCode.ERROR_UNEXPECTED, msg); 775 } 776 777 logger.debug(INFO_UPGRADE_REBUILD_INDEX_ENDS); 778 context.notifyProgress(pnc.setProgress(100)); 779 } 780 781 @Override 782 public String toString() 783 { 784 return "Post upgrade rebuild indexes task"; 785 } 786 }; 787 } 788 789 /** 790 * Creates a file object representing config/upgrade/schema.ldif.current which 791 * the server creates the first time it starts if there are schema 792 * customizations. 793 * 794 * @return An upgrade task which upgrade the config/upgrade folder, creating a 795 * new schema.ldif.rev which is needed after schema customization for 796 * starting correctly the server. 797 */ 798 public static UpgradeTask updateConfigUpgradeFolder() 799 { 800 return new AbstractUpgradeTask() 801 { 802 @Override 803 public void perform(final UpgradeContext context) throws ClientException 804 { 805 final LocalizableMessage msg = INFO_UPGRADE_TASK_REFRESH_UPGRADE_DIRECTORY.get(); 806 logger.debug(msg); 807 808 final ProgressNotificationCallback pnc = new ProgressNotificationCallback(INFORMATION, msg, 20); 809 context.notifyProgress(pnc); 810 811 try 812 { 813 String toRevision = context.getToVersion().getRevision(); 814 updateConfigUpgradeSchemaFile(configSchemaDirectory, toRevision); 815 816 context.notifyProgress(pnc.setProgress(100)); 817 } 818 catch (final Exception ex) 819 { 820 throw unexpectedException(context, pnc, ERR_UPGRADE_CONFIG_ERROR_UPGRADE_FOLDER.get(ex.getMessage())); 821 } 822 } 823 824 @Override 825 public String toString() 826 { 827 return INFO_UPGRADE_TASK_REFRESH_UPGRADE_DIRECTORY.get().toString(); 828 } 829 }; 830 } 831 832 /** 833 * Renames the SNMP security config file if it exists. Since 2.5.0.7466 this 834 * file has been renamed. 835 * 836 * @param summary 837 * The summary of this upgrade task. 838 * @return An upgrade task which renames the old SNMP security config file if 839 * it exists. 840 */ 841 public static UpgradeTask renameSnmpSecurityConfig(final LocalizableMessage summary) 842 { 843 return new AbstractUpgradeTask() 844 { 845 @Override 846 public void perform(final UpgradeContext context) throws ClientException 847 { 848 /* 849 * Snmp config file contains old name in old version(like 2.4.5), in 850 * order to make sure the process will still work after upgrade, we need 851 * to rename it - only if it exists. 852 */ 853 final File snmpDir = UpgradeUtils.configSnmpSecurityDirectory; 854 if (snmpDir.exists()) 855 { 856 ProgressNotificationCallback pnc = new ProgressNotificationCallback(INFORMATION, summary, 0); 857 try 858 { 859 final File oldSnmpConfig = new File(snmpDir, "opends-snmp.security"); 860 if (oldSnmpConfig.exists()) 861 { 862 context.notifyProgress(pnc.setProgress(20)); 863 logger.debug(summary); 864 865 final File snmpConfig = new File(snmpDir, "opendj-snmp.security"); 866 FileManager.rename(oldSnmpConfig, snmpConfig); 867 868 context.notifyProgress(pnc.setProgress(100)); 869 } 870 } 871 catch (final Exception ex) 872 { 873 LocalizableMessage msg = ERR_UPGRADE_RENAME_SNMP_SECURITY_CONFIG_FILE.get(ex.getMessage()); 874 throw unexpectedException(context, pnc, msg); 875 } 876 } 877 } 878 879 @Override 880 public String toString() 881 { 882 return String.valueOf(summary); 883 } 884 }; 885 } 886 887 /** 888 * Removes the specified file from the file-system. 889 * 890 * @param file 891 * The file to be removed. 892 * @return An upgrade task which removes the specified file from the file-system. 893 */ 894 public static UpgradeTask deleteFile(final File file) 895 { 896 return new AbstractUpgradeTask() 897 { 898 @Override 899 public void perform(UpgradeContext context) throws ClientException 900 { 901 LocalizableMessage msg = INFO_UPGRADE_TASK_DELETE_FILE.get(file); 902 ProgressNotificationCallback pnc = new ProgressNotificationCallback(INFORMATION, msg, 0); 903 context.notifyProgress(pnc); 904 try 905 { 906 FileManager.deleteRecursively(file); 907 context.notifyProgress(pnc.setProgress(100)); 908 } 909 catch (Exception e) 910 { 911 throw unexpectedException(context, pnc, LocalizableMessage.raw(e.getMessage())); 912 } 913 } 914 915 @Override 916 public String toString() 917 { 918 return INFO_UPGRADE_TASK_DELETE_FILE.get(file).toString(); 919 } 920 }; 921 } 922 923 /** 924 * Creates an upgrade task which is responsible for preparing local-db backend JE databases for a full rebuild once 925 * they have been converted to pluggable JE backends. 926 * 927 * @return An upgrade task which is responsible for preparing local-db backend JE databases. 928 */ 929 public static UpgradeTask migrateLocalDBBackendsToJEBackends() { 930 return new AbstractUpgradeTask() { 931 /** Properties of JE backends to be migrated. */ 932 class Backend { 933 final String id; 934 final boolean isEnabled; 935 final Set<DN> baseDNs; 936 final File envDir; 937 final Map<String, String> renamedDbs = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); 938 939 private Backend(Entry config) { 940 id = config.parseAttribute("ds-cfg-backend-id").asString(); 941 isEnabled = config.parseAttribute("ds-cfg-enabled").asBoolean(false); 942 baseDNs = config.parseAttribute("ds-cfg-base-dn").asSetOfDN(); 943 String dbDirectory = config.parseAttribute("ds-cfg-db-directory").asString(); 944 File backendParentDirectory = new File(dbDirectory); 945 if (!backendParentDirectory.isAbsolute()) { 946 backendParentDirectory = new File(getInstancePath(), dbDirectory); 947 } 948 envDir = new File(backendParentDirectory, id); 949 for (String db : Arrays.asList("compressed_attributes", "compressed_object_classes")) { 950 renamedDbs.put(db, new TreeName("compressed_schema", db).toString()); 951 } 952 for (DN baseDN : baseDNs) { 953 renamedDbs.put(oldName(baseDN), newName(baseDN)); 954 } 955 } 956 } 957 958 private final List<Backend> backends = new LinkedList<>(); 959 960 /** 961 * Finds all the existing JE backends and determines if they can be migrated or not. It will not be possible to 962 * migrate a JE backend if the id2entry database name cannot easily be determined, which may happen because 963 * matching rules have changed significantly in 3.0.0. 964 */ 965 @Override 966 public void prepare(final UpgradeContext context) throws ClientException { 967 // Requires answer from the user. 968 if (context.confirmYN(INFO_UPGRADE_TASK_MIGRATE_JE_DESCRIPTION.get(), NO) != YES) { 969 throw new ClientException(ReturnCode.ERROR_USER_CANCELLED, 970 INFO_UPGRADE_TASK_MIGRATE_JE_CANCELLED.get()); 971 } 972 973 final SearchRequest sr = Requests.newSearchRequest("", SearchScope.WHOLE_SUBTREE, 974 "(objectclass=ds-cfg-local-db-backend)"); 975 try (final EntryReader entryReader = searchConfigFile(sr)) { 976 // Abort the upgrade if there are JE backends but no JE library. 977 if (entryReader.hasNext() && !isJeLibraryAvailable()) { 978 throw new ClientException(ReturnCode.CONSTRAINT_VIOLATION, INFO_UPGRADE_TASK_MIGRATE_JE_NO_JE_LIB.get()); 979 } 980 while (entryReader.hasNext()) { 981 Backend backend = new Backend(entryReader.readEntry()); 982 if (backend.isEnabled) { 983 abortIfBackendCannotBeMigrated(backend); 984 } 985 backends.add(backend); 986 } 987 } catch (IOException e) { 988 throw new ClientException(ReturnCode.APPLICATION_ERROR, INFO_UPGRADE_TASK_MIGRATE_CONFIG_READ_FAIL.get(), e); 989 } 990 } 991 992 private void abortIfBackendCannotBeMigrated(final Backend backend) throws ClientException { 993 Set<String> existingDatabases = JEHelper.listDatabases(backend.envDir); 994 for (DN baseDN : backend.baseDNs) { 995 final String oldName = oldName(baseDN); 996 if (!existingDatabases.contains(oldName)) { 997 LocalizableMessage msg = INFO_UPGRADE_TASK_MIGRATE_JE_UGLY_DN.get(backend.id, baseDN); 998 throw new ClientException(ReturnCode.CONSTRAINT_VIOLATION, msg); 999 } 1000 } 1001 } 1002 1003 /** 1004 * Renames the compressed schema indexes and id2entry in a 2.x environment to 1005 * the naming scheme used in 3.0.0. Before 3.0.0 JE databases were named as follows: 1006 * 1007 * 1) normalize the base DN 1008 * 2) replace all non-alphanumeric characters with '_' 1009 * 3) append '_' 1010 * 4) append the index name. 1011 * 1012 * For example, id2entry in the base DN dc=white space,dc=com would be named 1013 * dc_white_space_dc_com_id2entry. In 3.0.0 JE databases are named as follows: 1014 * 1015 * 1) normalize the base DN and URL encode it (' ' are converted to %20) 1016 * 2) format as '/' + URL encoded base DN + '/' + index name. 1017 * 1018 * The matching rules in 3.0.0 are not compatible with previous versions, so we need 1019 * to do a best effort attempt to figure out the old database name from a given base DN. 1020 */ 1021 @Override 1022 public void perform(final UpgradeContext context) throws ClientException { 1023 if (!isJeLibraryAvailable()) { 1024 return; 1025 } 1026 1027 for (Backend backend : backends) { 1028 if (backend.isEnabled) { 1029 ProgressNotificationCallback pnc = new ProgressNotificationCallback( 1030 INFORMATION, INFO_UPGRADE_TASK_MIGRATE_JE_SUMMARY_1.get(backend.id), 0); 1031 context.notifyProgress(pnc); 1032 try { 1033 JEHelper.migrateDatabases(backend.envDir, backend.renamedDbs); 1034 context.notifyProgress(pnc.setProgress(100)); 1035 } catch (ClientException e) { 1036 throw unexpectedException(context, pnc, e.getMessageObject()); 1037 } 1038 } else { 1039 // Skip backends which have been disabled. 1040 final ProgressNotificationCallback pnc = new ProgressNotificationCallback( 1041 INFORMATION, INFO_UPGRADE_TASK_MIGRATE_JE_SUMMARY_5.get(backend.id), 0); 1042 context.notifyProgress(pnc); 1043 context.notifyProgress(pnc.setProgress(100)); 1044 } 1045 } 1046 } 1047 1048 private boolean isJeLibraryAvailable() { 1049 return isClassAvailable("com.sleepycat.je.Environment"); 1050 } 1051 1052 private String newName(final DN baseDN) { 1053 return new TreeName(baseDN.toNormalizedUrlSafeString(), "id2entry").toString(); 1054 } 1055 1056 private String oldName(final DN baseDN) { 1057 String s = baseDN.toString(); 1058 StringBuilder builder = new StringBuilder(); 1059 for (int i = 0; i < s.length(); i++) { 1060 char c = s.charAt(i); 1061 builder.append(Character.isLetterOrDigit(c) ? c : '_'); 1062 } 1063 builder.append("_id2entry"); 1064 return builder.toString(); 1065 } 1066 1067 @Override 1068 public String toString() 1069 { 1070 return INFO_UPGRADE_TASK_MIGRATE_JE_SUMMARY_1.get("%s").toString(); 1071 } 1072 }; 1073 } 1074 1075 /** 1076 * Creates backups of the local DB backends directories by renaming adding them a ".bak" suffix. 1077 * e.g "userRoot" would become "userRoot.bak" 1078 * 1079 * @param backendObjectClass 1080 * The backend object class name. 1081 */ 1082 static UpgradeTask renameLocalDBBackendDirectories(final String backendObjectClass) 1083 { 1084 return new AbstractUpgradeTask() 1085 { 1086 private boolean reimportRequired; 1087 1088 @Override 1089 public void perform(UpgradeContext context) throws ClientException 1090 { 1091 try 1092 { 1093 Filter filter = Filter.equality("objectclass", backendObjectClass); 1094 SearchRequest findLocalDBBackends = Requests.newSearchRequest(DN.rootDN(), SearchScope.WHOLE_SUBTREE, filter); 1095 try (final EntryReader jeBackends = searchConfigFile(findLocalDBBackends)) 1096 { 1097 while (jeBackends.hasNext()) 1098 { 1099 Upgrade.needToRunPostUpgradePhase(); 1100 reimportRequired = true; 1101 1102 Entry jeBackend = jeBackends.readEntry(); 1103 File dbParent = UpgradeUtils.getFileForPath(jeBackend.parseAttribute("ds-cfg-db-directory").asString()); 1104 String id = jeBackend.parseAttribute("ds-cfg-backend-id").asString(); 1105 1106 // Use canonical paths so that the progress message is more readable. 1107 File dbDirectory = new File(dbParent, id).getCanonicalFile(); 1108 File dbDirectoryBackup = new File(dbParent, id + ".bak").getCanonicalFile(); 1109 if (dbDirectory.exists() && !dbDirectoryBackup.exists()) 1110 { 1111 LocalizableMessage msg = INFO_UPGRADE_TASK_RENAME_JE_DB_DIR.get(dbDirectory, dbDirectoryBackup); 1112 ProgressNotificationCallback pnc = new ProgressNotificationCallback(0, msg, 0); 1113 context.notifyProgress(pnc); 1114 boolean renameSucceeded = dbDirectory.renameTo(dbDirectoryBackup); 1115 context.notifyProgress(pnc.setProgress(renameSucceeded ? 100 : -1)); 1116 } 1117 } 1118 } 1119 } 1120 catch (Exception e) 1121 { 1122 logger.error(LocalizableMessage.raw(e.getMessage())); 1123 } 1124 } 1125 1126 @Override 1127 public void postUpgrade(UpgradeContext context) throws ClientException 1128 { 1129 postponePostUpgrade(context); 1130 } 1131 1132 @Override 1133 public void postponePostUpgrade(UpgradeContext context) throws ClientException 1134 { 1135 if (reimportRequired) 1136 { 1137 context.notify(INFO_UPGRADE_TASK_RENAME_JE_DB_DIR_WARNING.get(), TextOutputCallback.WARNING); 1138 } 1139 } 1140 1141 @Override 1142 public String toString() 1143 { 1144 return INFO_UPGRADE_TASK_RENAME_JE_DB_DIR.get("%s", "%s").toString(); 1145 } 1146 }; 1147 } 1148 1149 /** This inner classes causes JE to be lazily linked and prevents runtime errors if JE is not in the classpath. */ 1150 private static final class JEHelper { 1151 private static ClientException clientException(final File backendDirectory, final DatabaseException e) { 1152 logger.error(LocalizableMessage.raw(StaticUtils.stackTraceToString(e))); 1153 return new ClientException(ReturnCode.CONSTRAINT_VIOLATION, 1154 INFO_UPGRADE_TASK_MIGRATE_JE_ENV_UNREADABLE.get(backendDirectory), e); 1155 } 1156 1157 private static Set<String> listDatabases(final File backendDirectory) throws ClientException { 1158 try (Environment je = new Environment(backendDirectory, null)) { 1159 Set<String> databases = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); 1160 databases.addAll(je.getDatabaseNames()); 1161 return databases; 1162 } catch (DatabaseException e) { 1163 throw clientException(backendDirectory, e); 1164 } 1165 } 1166 1167 private static void migrateDatabases(final File envDir, final Map<String, String> renamedDbs) 1168 throws ClientException { 1169 EnvironmentConfig config = new EnvironmentConfig().setTransactional(true); 1170 try (Environment je = new Environment(envDir, config)) { 1171 final Transaction txn = je.beginTransaction(null, new TransactionConfig()); 1172 try { 1173 for (String dbName : je.getDatabaseNames()) { 1174 String newDbName = renamedDbs.get(dbName); 1175 if (newDbName != null) { 1176 // id2entry or compressed schema should be kept 1177 je.renameDatabase(txn, dbName, newDbName); 1178 } else { 1179 // This index will need rebuilding 1180 je.removeDatabase(txn, dbName); 1181 } 1182 } 1183 txn.commit(); 1184 } finally { 1185 txn.abort(); 1186 } 1187 } catch (DatabaseException e) { 1188 throw JEHelper.clientException(envDir, e); 1189 } 1190 } 1191 } 1192 1193 private static void displayChangeCount(final File configfile, final int changeCount) 1194 { 1195 String fileName = configfile.getAbsolutePath(); 1196 if (changeCount != 0) 1197 { 1198 logger.debug(INFO_UPGRADE_CHANGE_DONE_IN_SPECIFIC_FILE, fileName, changeCount); 1199 } 1200 else 1201 { 1202 logger.debug(INFO_UPGRADE_NO_CHANGE_DONE_IN_SPECIFIC_FILE, fileName); 1203 } 1204 } 1205 1206 private static void displayTaskLogInformation(final LocalizableMessage summary, 1207 final String filter, final String... ldif) 1208 { 1209 logger.debug(summary); 1210 if (filter != null) 1211 { 1212 logger.debug(LocalizableMessage.raw(filter)); 1213 } 1214 if (ldif != null) 1215 { 1216 logger.debug(LocalizableMessage.raw(Arrays.toString(ldif))); 1217 } 1218 } 1219 1220 private static ClientException unexpectedException(final UpgradeContext context, 1221 final ProgressNotificationCallback pnc, final LocalizableMessage message) throws ClientException 1222 { 1223 countErrors++; 1224 context.notifyProgress(pnc.setProgress(-100)); 1225 return new ClientException(ReturnCode.ERROR_UNEXPECTED, message); 1226 } 1227 1228 static void handleClientException(final UpgradeContext context, ClientException e) throws ClientException 1229 { 1230 logger.error(e.getMessageObject()); 1231 if (!context.isIgnoreErrorsMode()) 1232 { 1233 throw e; 1234 } 1235 } 1236 1237 private static UpgradeTask updateConfigEntry(final LocalizableMessage summary, final String filter, 1238 final ChangeOperationType changeOperationType, final String... ldif) 1239 { 1240 return new AbstractUpgradeTask() 1241 { 1242 @Override 1243 public void perform(final UpgradeContext context) throws ClientException 1244 { 1245 performConfigFileUpdate(summary, filter, changeOperationType, context, ldif); 1246 } 1247 1248 @Override 1249 public String toString() 1250 { 1251 return String.valueOf(summary); 1252 } 1253 }; 1254 } 1255 1256 private static void performConfigFileUpdate(final LocalizableMessage summary, final String filter, 1257 final ChangeOperationType changeOperationType, final UpgradeContext context, final String... ldif) 1258 throws ClientException 1259 { 1260 displayTaskLogInformation(summary, filter, ldif); 1261 1262 final ProgressNotificationCallback pnc = new ProgressNotificationCallback(INFORMATION, summary, 20); 1263 context.notifyProgress(pnc); 1264 1265 try 1266 { 1267 final Filter filterVal = filter != null ? Filter.valueOf(filter) : null; 1268 final int changeCount = updateConfigFile(configFile, filterVal, changeOperationType, ldif); 1269 displayChangeCount(configFile, changeCount); 1270 1271 context.notifyProgress(pnc.setProgress(100)); 1272 } 1273 catch (final Exception e) 1274 { 1275 throw unexpectedException(context, pnc, LocalizableMessage.raw(e.getMessage())); 1276 } 1277 } 1278 1279 static UpgradeTask clearReplicationDbDirectory() 1280 { 1281 return new AbstractUpgradeTask() 1282 { 1283 private File replicationDbDir; 1284 1285 @Override 1286 public void prepare(final UpgradeContext context) throws ClientException 1287 { 1288 String replDbDir = readReplicationDbDirFromConfig(); 1289 if (replDbDir != null 1290 && context.confirmYN(INFO_UPGRADE_TASK_MIGRATE_CHANGELOG_DESCRIPTION.get(), NO) == YES) 1291 { 1292 replicationDbDir = new File(getInstancePath(), replDbDir).getAbsoluteFile(); 1293 } 1294 // if replDbDir == null, then this is not an RS, there is no changelog DB to clear 1295 } 1296 1297 private String readReplicationDbDirFromConfig() throws ClientException 1298 { 1299 final SearchRequest sr = Requests.newSearchRequest( 1300 DN.valueOf("cn=replication server,cn=Multimaster Synchronization,cn=Synchronization Providers,cn=config"), 1301 SearchScope.BASE_OBJECT, Filter.alwaysTrue()); 1302 try (final EntryReader entryReader = searchConfigFile(sr)) 1303 { 1304 if (entryReader.hasNext()) 1305 { 1306 final Entry replServerCfg = entryReader.readEntry(); 1307 return replServerCfg.parseAttribute("ds-cfg-replication-db-directory").asString(); 1308 } 1309 return null; 1310 } 1311 catch (IOException e) 1312 { 1313 LocalizableMessage msg = INFO_UPGRADE_TASK_MIGRATE_CONFIG_READ_FAIL.get(); 1314 throw new ClientException(ReturnCode.APPLICATION_ERROR, msg, e); 1315 } 1316 } 1317 1318 @Override 1319 public void perform(final UpgradeContext context) throws ClientException 1320 { 1321 if (replicationDbDir == null) 1322 { 1323 // there is no changelog DB to clear 1324 return; 1325 } 1326 1327 LocalizableMessage msg = INFO_UPGRADE_TASK_DELETE_CHANGELOG_SUMMARY.get(replicationDbDir); 1328 ProgressNotificationCallback pnc = new ProgressNotificationCallback(INFORMATION, msg, 0); 1329 context.notifyProgress(pnc); 1330 try 1331 { 1332 FileManager.deleteRecursively(replicationDbDir); 1333 context.notifyProgress(pnc.setProgress(100)); 1334 } 1335 catch (ClientException e) 1336 { 1337 throw unexpectedException(context, pnc, e.getMessageObject()); 1338 } 1339 catch (Exception e) 1340 { 1341 throw unexpectedException(context, pnc, LocalizableMessage.raw(e.getLocalizedMessage())); 1342 } 1343 } 1344 1345 @Override 1346 public String toString() 1347 { 1348 return INFO_UPGRADE_TASK_DELETE_CHANGELOG_SUMMARY.get(replicationDbDir).toString(); 1349 } 1350 }; 1351 } 1352 1353 /** Removes server and localized jars from previous version since names have changed. */ 1354 static UpgradeTask removeOldJarFiles() 1355 { 1356 return new AbstractUpgradeTask() 1357 { 1358 1359 @Override 1360 public void perform(final UpgradeContext context) throws ClientException 1361 { 1362 final ProgressNotificationCallback pnc = new ProgressNotificationCallback( 1363 INFORMATION, INFO_UPGRADE_TASK_REMOVE_OLD_JARS.get(), 0); 1364 context.notifyProgress(pnc); 1365 1366 final boolean fileSystemIsCaseSensitive = fileSystemIsCaseSensitive(context); 1367 1368 deleteJarFilesIfFileSystemIsCaseSensitive(fileSystemIsCaseSensitive, "OpenDJ"); 1369 for (final String locale : SUPPORTED_LOCALES_FOR_3_0_0) 1370 { 1371 deleteJarFiles("OpenDJ-" + locale); 1372 deleteJarFilesIfFileSystemIsCaseSensitive(fileSystemIsCaseSensitive, "OpenDJ_" + locale); 1373 } 1374 // Jar files from 2.6.x 1375 deleteJarFiles("jackson-core-asl", "jackson-mapper-asl", "json-fluent", "json-resource-servlet", 1376 "mail", "opendj-ldap-sdk", "opendj-rest2ldap-servlet", "opendj-server2x-adapter"); 1377 context.notifyProgress(pnc.setProgress(100)); 1378 } 1379 1380 private void deleteJarFilesIfFileSystemIsCaseSensitive( 1381 final boolean fileSystemIsCaseSensitive, final String... jarFileNames) 1382 { 1383 if (fileSystemIsCaseSensitive) 1384 { 1385 deleteJarFiles(jarFileNames); 1386 } 1387 } 1388 1389 private void deleteJarFiles(final String... jarFileNames) 1390 { 1391 for (final String jarFileName : jarFileNames) 1392 { 1393 final File f = new File(libDirectory, jarFileName + ".jar"); 1394 if (f.exists()) 1395 { 1396 f.delete(); 1397 } 1398 } 1399 } 1400 1401 /** Used to know if we have to remove old "camel case" OpenDJ[_]*.jar(see OPENDJ-2692). */ 1402 private boolean fileSystemIsCaseSensitive(final UpgradeContext context) throws ClientException 1403 { 1404 final File openDJCamelCaseJar = new File(libDirectory, "OpenDJ.jar"); 1405 try 1406 { 1407 // getCanonicalPath() will return the new "opendj.jar" on case insensitive file systems 1408 return openDJCamelCaseJar.getCanonicalPath().equals(openDJCamelCaseJar.getAbsolutePath()); 1409 } 1410 catch (final IOException unlikely) 1411 { 1412 // Warn the user that he may have some old camel case jars to remove 1413 context.notify(INFO_UPGRADE_TASK_UNABLE_TO_REMOVE_OLD_JARS.get()); 1414 Upgrade.needToExitWithErrorCode(); 1415 return false; 1416 } 1417 } 1418 }; 1419 } 1420 1421 /** Prevent instantiation. */ 1422 private UpgradeTasks() 1423 { 1424 // Do nothing. 1425 } 1426 1427 /** 1428 * This task exists because OpenDJ 3.0.0 added an attribute type definition for 1429 * {@code ds-cfg-csv-delimiter-char}, but unfortunately trailing spaces existed after the closing 1430 * parenthesis. As a consequence, this definition was not added to the concatenated schema. 1431 * <p> 1432 * This task restores this definition in the concatenated schema using the following algorithm: 1433 * <p> 1434 * If {@code ds-cfg-csv-delimiter-char} attribute type definition exists in 02-config.ldif, 1435 * but not in the concatenated schema then append its definition to the concatenated schema, 1436 * omitting the trailing spaces. 1437 * 1438 * @return The relevant upgrade task 1439 * @see OPENDJ-3081 1440 */ 1441 static UpgradeTask restoreCsvDelimiterAttributeTypeInConcatenatedSchemaFile() 1442 { 1443 return new AbstractUpgradeTask() 1444 { 1445 private boolean shouldRunTask; 1446 1447 @Override 1448 public void prepare(UpgradeContext context) throws ClientException 1449 { 1450 shouldRunTask = concatenatedSchemaFile.exists(); 1451 } 1452 1453 @Override 1454 public void perform(UpgradeContext context) throws ClientException 1455 { 1456 if (!shouldRunTask) 1457 { 1458 return; 1459 } 1460 final ProgressNotificationCallback pnc = new ProgressNotificationCallback(INFORMATION, getSummary(), 0); 1461 1462 final File configFile = new File(configSchemaDirectory, "02-config.ldif"); 1463 AttributeType configCsvCharAT = readCsvDelimiterCharAttributeType(configFile, context, pnc); 1464 context.notifyProgress(pnc.setProgress(33)); 1465 1466 AttributeType concatenatedCsvCharAT = readCsvDelimiterCharAttributeType(concatenatedSchemaFile, context, pnc); 1467 context.notifyProgress(pnc.setProgress(66)); 1468 1469 if (!configCsvCharAT.isPlaceHolder() && concatenatedCsvCharAT.isPlaceHolder()) 1470 { 1471 final String csvCharAttrTypeDefinition = configCsvCharAT.toString().trim(); 1472 try (BufferedWriter writer = Files.newBufferedWriter(concatenatedSchemaFile.toPath(), UTF_8, APPEND)) 1473 { 1474 writer.append(CoreSchema.getAttributeTypesAttributeType().getNameOrOID()); 1475 writer.append(": "); 1476 writer.append(addSchemaFileToElementDefinitionIfAbsent(csvCharAttrTypeDefinition, "02-config.ldif")); 1477 writer.newLine(); 1478 } 1479 catch (IOException e) 1480 { 1481 throw unexpectedException(context, pnc, INFO_UPGRADE_TASK_CANNOT_WRITE_TO_CONCATENATED_SCHEMA_FILE.get( 1482 concatenatedSchemaFile.toPath(), stackTraceToSingleLineString(e))); 1483 } 1484 } 1485 context.notifyProgress(pnc.setProgress(100)); 1486 } 1487 1488 private AttributeType readCsvDelimiterCharAttributeType(final File schemaFile, 1489 final UpgradeContext context, final ProgressNotificationCallback pnc) throws ClientException 1490 { 1491 final Schema coreSchema = Schema.getCoreSchema(); 1492 try (EntryReader entryReader = new LDIFEntryReader(new FileReader(schemaFile))) 1493 { 1494 final Entry schemaEntry = entryReader.readEntry(); 1495 final SchemaBuilder builder = new SchemaBuilder(); 1496 for (Syntax syntax : coreSchema.getSyntaxes()) 1497 { 1498 builder.buildSyntax(syntax).addToSchema(); 1499 } 1500 for (MatchingRule rule : coreSchema.getMatchingRules()) 1501 { 1502 builder.buildMatchingRule(rule).addToSchema(); 1503 } 1504 return builder 1505 .addSchema(schemaEntry, false) 1506 .toSchema() 1507 .asNonStrictSchema() 1508 .getAttributeType("ds-cfg-csv-delimiter-char"); 1509 } 1510 catch (IOException e) 1511 { 1512 throw unexpectedException(context, pnc, INFO_UPGRADE_TASK_CANNOT_READ_SCHEMA_FILE.get( 1513 schemaFile.getAbsolutePath(), stackTraceToSingleLineString(e))); 1514 } 1515 } 1516 1517 private LocalizableMessage getSummary() 1518 { 1519 return INFO_UPGRADE_TASK_SUMMARY_RESTORE_CSV_DELIMITER_CHAR.get(); 1520 } 1521 1522 @Override 1523 public String toString() 1524 { 1525 return getSummary().toString(); 1526 } 1527 }; 1528 } 1529}