/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2013 ForgeRock AS */ package org.opends.server.tools.upgrade; import static org.opends.messages.ToolMessages.*; import static org.opends.server.tools.ToolConstants.OPTION_LONG_FORCE_UPGRADE; import static org.opends.server.tools.ToolConstants.OPTION_LONG_NO_PROMPT; import static org.opends.server.tools.upgrade.FileManager.copy; import static org.opends.server.tools.upgrade.Upgrade.*; import static org.opends.server.tools.upgrade.UpgradeUtils.*; import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.logging.Level; import java.util.logging.Logger; import javax.security.auth.callback.ConfirmationCallback; import org.opends.messages.Message; import org.opends.server.controls.PersistentSearchChangeType; import org.opends.server.protocols.ldap.LDAPFilter; import org.opends.server.tools.ClientException; import org.opends.server.tools.upgrade.UpgradeTask.TaskType; /** * Factory methods for create new upgrade tasks. */ public final class UpgradeTasks { /** * An errors counter in case of ignore errors mode. */ static int countErrors = 0; /** * Upgrade's logger. */ static private final Logger LOG = Logger .getLogger(UpgradeCli.class.getName()); /** * Returns a new upgrade task which applies an LDIF record to all * configuration entries matching the provided filter. * * @param summary * The summary of this upgrade task. * @param ldif * The LDIF record which will be applied to matching entries. * @return A new upgrade task which applies an LDIF record to all * configuration entries matching the provided filter. */ public static UpgradeTask addConfigEntry(final Message summary, final String... ldif) { return addConfigEntry0(summary, summary, false, ldif); } /** * Returns a new upgrade task which applies an LDIF record to all * configuration entries matching the provided filter. * * @param summary * The summary of this upgrade task. * @param description * The detailed description of this upgrade task. * @param ldif * The LDIF record which will be applied to matching entries. * @return A new upgrade task which applies an LDIF record to all * configuration entries matching the provided filter. */ public static UpgradeTask addConfigEntryOptional(final Message summary, final Message description, final String... ldif) { return addConfigEntry0(summary, description, true, ldif); } /** * This task copies the file placed in parameter within the config / schema * folder. If the file already exists, it's overwritten. * * @param fileName * The name of the file which need to be copied. * @return A task which copy the the file placed in parameter within the * config / schema folder. If the file already exists, it's * overwritten. */ public static UpgradeTask copySchemaFile(final String fileName) { return new AbstractUpgradeTask() { @Override public void perform(final UpgradeContext context) throws ClientException { final Message msg = INFO_UPGRADE_TASK_REPLACE_SCHEMA_FILE.get(fileName); LOG.log(Level.INFO, msg.toString()); final ProgressNotificationCallback pnc = new ProgressNotificationCallback(0, msg, 0); final File schemaFileTemplate = new File(templateConfigSchemaDirectory, fileName); try { context.notifyProgress(pnc.changeProgress(20)); copy(schemaFileTemplate, configSchemaDirectory, true); context.notifyProgress(pnc.changeProgress(100)); } catch (final IOException e) { manageTaskException(context, ERR_UPGRADE_COPYSCHEMA_FAILS.get( schemaFileTemplate.getName(), e.getMessage()), pnc); } } }; } /** * This task copies the file placed in parameter within the config folder. If * the file already exists, it's overwritten. * * @param fileName * The name of the file which need to be copied. * @return A task which copy the the file placed in parameter within the * config folder. If the file already exists, it's overwritten. */ public static UpgradeTask addConfigFile(final String fileName) { return new AbstractUpgradeTask() { @Override public void perform(final UpgradeContext context) throws ClientException { final Message msg = INFO_UPGRADE_TASK_ADD_CONFIG_FILE.get(fileName); LOG.log(Level.INFO, msg.toString()); final ProgressNotificationCallback pnc = new ProgressNotificationCallback(0, msg, 0); final File configFile = new File(templateConfigDirectory, fileName); try { context.notifyProgress(pnc.changeProgress(20)); copy(configFile, configDirectory, true); context.notifyProgress(pnc.changeProgress(100)); } catch (final IOException e) { manageTaskException(context, ERR_UPGRADE_ADD_CONFIG_FILE_FAILS.get( configFile.getName(), e.getMessage()), pnc); } } }; } /** * Returns a new upgrade task which applies an LDIF record to all * configuration entries matching the provided filter. * * @param summary * The summary of this upgrade task. * @param filter * The LDAP filter which configuration entries must match. * @param ldif * The LDIF record which will be applied to matching entries. * @return A new upgrade task which applies an LDIF record to all * configuration entries matching the provided filter. */ public static UpgradeTask modifyConfigEntry(final Message summary, final String filter, final String... ldif) { return modifyConfigEntry(summary, summary, false, filter, ldif); } /** * Returns a new upgrade task which applies an LDIF record to all * configuration entries matching the provided filter. * * @param summary * The summary of this upgrade task. * @param description * The detailed description of this upgrade task. * @param filter * The LDAP filter which configuration entries must match. * @param ldif * The LDIF record which will be applied to matching entries. * @return A new upgrade task which applies an LDIF record to all * configuration entries matching the provided filter. */ public static UpgradeTask modifyConfigEntryOptional(final Message summary, final Message description, final String filter, final String... ldif) { return modifyConfigEntry(summary, description, true, filter, ldif); } /** * This task adds a new attribute type (must exists in the original file) to * the specified file placed in parameter. The destination must be a file * contained in the config/schema folder. E.g : This example adds a new * attribute type named 'etag' in the 00.core.ldif. The 'etag' attribute * already exists in the 00-core.ldif template schema file. * *
   * register("2.5.0.7192",
   *   newAttributeTypes(Message.raw("New attribute etag"),
   *   false, "00-core.ldif", "etag"));
   * 
* * @param summary * The summary of the task. * @param fileName * The file where to add the new attribute types. This file must be * contained in the config/schema folder. * @param names * The names of the new attributes to add to. * @return An upgrade task which adds new attribute types, defined previously * in the config template files, reads the definition and adds it onto * the specified file in parameter. */ public static UpgradeTask newAttributeTypes(final Message summary, final String fileName, final String... names) { return new AbstractUpgradeTask() { @Override public void perform(final UpgradeContext context) throws ClientException { LOG.log(Level.INFO, summary.toString()); final ProgressNotificationCallback pnc = new ProgressNotificationCallback(0, summary, 20); context.notifyProgress(pnc); final File schemaFileTemplate = new File(templateConfigSchemaDirectory, fileName); final File pathDestination = new File(configSchemaDirectory, fileName); try { final int changeCount = updateSchemaFile(schemaFileTemplate, pathDestination, names, null); displayChangeCount(pathDestination.getPath(), changeCount); context.notifyProgress(pnc.changeProgress(100)); } catch (final IOException e) { manageTaskException(context, ERR_UPGRADE_ADDATTRIBUTE_FAILS.get( schemaFileTemplate.getName(), e.getMessage()), pnc); } } }; } /** * This task adds a new object class (must exists in the original file) to the * specified file placed in parameter. The destination must be a file * contained in the config/schema folder. * * @param summary * The summary of the task. * @param fileName * The file where to add the new object classes. This file must be * contained in the config/schema folder. * @param names * The names of the new object classes to add to. * @return An upgrade task which adds new object classes, defined previously * in the config template files, reads the definition and adds it onto * the specified file in parameter. */ public static UpgradeTask newObjectClasses(final Message summary, final String fileName, final String... names) { return new AbstractUpgradeTask() { @Override public void perform(final UpgradeContext context) throws ClientException { LOG.log(Level.INFO, summary.toString()); final ProgressNotificationCallback pnc = new ProgressNotificationCallback(0, summary, 20); context.notifyProgress(pnc); final File schemaFileTemplate = new File(templateConfigSchemaDirectory, fileName); final File pathDestination = new File(configSchemaDirectory, fileName); context.notifyProgress(pnc.changeProgress(20)); try { final int changeCount = updateSchemaFile(schemaFileTemplate, pathDestination, null, names); displayChangeCount(pathDestination.getPath(), changeCount); context.notifyProgress(pnc.changeProgress(100)); } catch (final IOException e) { manageTaskException(context, ERR_UPGRADE_ADDOBJECTCLASS_FAILS.get( schemaFileTemplate.getName(), e.getMessage()), pnc); } } }; } /** * Creates a rebuild all indexes task. * * @param summary * The summary of this upgrade task. * @return An Upgrade task which rebuild all the indexes. */ public static UpgradeTask rebuildAllIndexes(final Message summary) { return new UpgradeTask() { @Override public void end(final UpgradeContext context) throws ClientException { // Nothing to do. } @Override public void interact(final UpgradeContext context) throws ClientException { // Nothing to do. } @Override public void perform(final UpgradeContext context) throws ClientException { // TODO } @Override public void start(final UpgradeContext context) throws ClientException { context.notify(summary); } @Override public void verify(final UpgradeContext context) throws ClientException { verifyTaskType(TaskType.MANDATORY_USER_INTERACTION, context); } }; } /** * Creates a file object representing config/upgrade/schema.ldif.current which * the server creates the first time it starts if there are schema * customizations. * * @return An upgrade task which upgrade the config/upgrade folder, creating a * new schema.ldif.rev which is needed after schema customization for * starting correctly the server. */ public static UpgradeTask updateConfigUpgradeFolder() { return new AbstractUpgradeTask() { @Override public void perform(final UpgradeContext context) throws ClientException { final Message msg = INFO_UPGRADE_TASK_REFRESH_UPGRADE_DIRECTORY.get(); LOG.log(Level.INFO, msg.toString()); final ProgressNotificationCallback pnc = new ProgressNotificationCallback(0, msg, 20); context.notifyProgress(pnc); try { updateConfigUpgradeSchemaFile(configSchemaDirectory, String .valueOf(context.getToVersion().getRevisionNumber())); context.notifyProgress(pnc.changeProgress(100)); } catch (final Exception ex) { manageTaskException(context, ERR_UPGRADE_CONFIG_ERROR_UPGRADE_FOLDER .get(ex.getMessage()), pnc); } } }; } private static UpgradeTask addConfigEntry0(final Message summary, final Message description, final boolean needsUserConfirmation, final String... ldif) { return new UpgradeTask() { private boolean userConfirmation = true; @Override public void end(final UpgradeContext context) { // Nothing to do: no cleanup required. } @Override public void interact(final UpgradeContext context) throws ClientException { if (needsUserConfirmation) { // Process needs to have user's response to perform the current // modification. final int answer = context.confirmYN(INFO_UPGRADE_TASK_NEEDS_USER_CONFIRM .get(description), ConfirmationCallback.YES); // The user refuses to perform this task. if (answer == ConfirmationCallback.NO) { userConfirmation = false; } } } @Override public void perform(final UpgradeContext context) throws ClientException { if (userConfirmation) { displayTaskLogInformation(summary.toString(), null, ldif); final ProgressNotificationCallback pnc = new ProgressNotificationCallback(0, summary, 20); context.notifyProgress(pnc); try { // TODO change the directory to the config if it exists. final File configFile = new File(configDirectory, Installation.CURRENT_CONFIG_FILE_NAME); final int changeCount = updateConfigFile(configFile.getPath(), null, PersistentSearchChangeType.ADD, ldif); displayChangeCount(configFile.getPath(), changeCount); context.notifyProgress(pnc.changeProgress(100)); } catch (final Exception e) { manageTaskException(context, Message.fromObject(e.getMessage()), pnc); } } } @Override public void start(final UpgradeContext context) throws ClientException { // Nothing to do. } @Override public void verify(final UpgradeContext context) throws ClientException { // Nothing to do. } }; } private static void displayChangeCount(final String fileName, final int changeCount) { if (changeCount != 0) { LOG.log(Level.INFO, INFO_UPGRADE_CHANGE_DONE_IN_SPECIFIC_FILE.get( fileName, String.valueOf(changeCount)).toString()); } else { LOG.log(Level.INFO, INFO_UPGRADE_NO_CHANGE_DONE_IN_SPECIFIC_FILE.get( fileName).toString()); } } private static void displayTaskLogInformation(final String summary, final String filter, final String... ldif) { LOG.log(Level.INFO, summary); if (filter != null) { LOG.log(Level.INFO, filter.toString()); } if (ldif != null) { LOG.log(Level.INFO, Arrays.asList(ldif).toString()); } } private static void manageTaskException(final UpgradeContext context, final Message message, final ProgressNotificationCallback pnc) throws ClientException { countErrors++; context.notifyProgress(pnc.changeProgress(-100)); LOG.log(Level.SEVERE, message.toString()); if (!context.isIgnoreErrorsMode()) { throw new ClientException(EXIT_CODE_ERROR, message); } } private static UpgradeTask modifyConfigEntry(final Message summary, final Message description, final boolean needsUserConfirmation, final String filter, final String... ldif) { return new UpgradeTask() { private boolean userConfirmation = true; @Override public void end(final UpgradeContext context) { // Nothing to do: no cleanup required. } @Override public void interact(final UpgradeContext context) throws ClientException { if (needsUserConfirmation) { // Process needs to have user's response to perform the current // modification. final int answer = context.confirmYN(INFO_UPGRADE_TASK_NEEDS_USER_CONFIRM .get(description), ConfirmationCallback.YES); // The user refuses to perform this task. if (answer == ConfirmationCallback.NO) { userConfirmation = false; } } } @Override public void perform(final UpgradeContext context) throws ClientException { if (userConfirmation) { displayTaskLogInformation(summary.toString(), filter, ldif); final ProgressNotificationCallback pnc = new ProgressNotificationCallback(0, summary, 20); context.notifyProgress(pnc); try { final File configFile = new File(configDirectory, Installation.CURRENT_CONFIG_FILE_NAME); final int changeCount = updateConfigFile(configFile.getPath(), LDAPFilter .decode(filter), PersistentSearchChangeType.MODIFY, ldif); displayChangeCount(configFile.getPath(), changeCount); context.notifyProgress(pnc.changeProgress(100)); } catch (final Exception e) { manageTaskException(context, Message.fromObject(e.getMessage()), pnc); } } } @Override public void start(final UpgradeContext context) throws ClientException { // Nothing to do. } @Override public void verify(final UpgradeContext context) throws ClientException { // Nothing to do. } }; } @SuppressWarnings("fallthrough") private static void verifyTaskType(final TaskType type, final UpgradeContext context) throws ClientException { /* * Checks CLI/GUI options via context. The process will stop * if user has selected conflicting options. */ switch (type) { case NEED_USER_INTERACTION: { // Nothing to do. break; } case MANDATORY_USER_INTERACTION: case TAKE_LONG_TIME_TO_COMPLETE: case CANNOT_BE_REVERTED: // The option is not present ? Stops the process. if (!context.isInteractiveMode() && !context.isForceUpgradeMode()) { context.notify(ERR_UPGRADE_USER_INTERACTION_REQUIRED.get( OPTION_LONG_NO_PROMPT, OPTION_LONG_FORCE_UPGRADE), FormattedNotificationCallback.NOTICE_CALLBACK); throw new ClientException(EXIT_CODE_MANUAL_INTERVENTION, ERR_UPGRADE_INVALID_USER_OPTIONS_SELECTED.get()); } default: break; } } // Prevent instantiation. private UpgradeTasks() { // Do nothing. } }