/*
* 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
*
*
* Portions Copyright 2007 Sun Microsystems, Inc.
*/
package org.opends.quicksetup.upgrader;
import org.opends.messages.Message;
import org.opends.messages.MessageBuilder;
import static org.opends.messages.QuickSetupMessages.*;
import org.opends.quicksetup.ApplicationException;
import org.opends.quicksetup.ReturnCode;
import org.opends.quicksetup.Installation;
import org.opends.quicksetup.UserInteraction;
import org.opends.quicksetup.Constants;
import org.opends.quicksetup.util.ExternalTools;
import org.opends.quicksetup.util.Utils;
import org.opends.quicksetup.util.OperationOutput;
import org.opends.quicksetup.util.InProcessServerController;
import org.opends.server.util.LDIFReader;
import org.opends.server.util.ChangeRecordEntry;
import org.opends.server.types.LDIFImportConfig;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This class handles migration of both schema and configuration
* for an installation that is in the process of being upgraded.
*
* This class is intented to be used in two differnt stages of the
* upgrade process: first, before installation's filesystem bits
* have been upgraded, a call to both {@link #calculateConfigCustomizations()}
* and {@link #calculateSchemaCustomizations()} should be made in
* order to capture what changes will need to be made to the server
* later on. Then once the filesystem bit have been upgraded calls to
* {@link #migrateConfiguration()} and {@link #migrateSchema()} are
* made to actually perform the migration.
*/
public class MigrationManager {
static private final Logger LOG =
Logger.getLogger(MigrationManager.class.getName());
/** Describes a particular component to be migrated. */
private enum Component { SCHEMA, CONFIGURATION }
private Installation installation;
private File backupDir;
private UserInteraction ui;
/**
* Creates a new parameterized instance.
* @param installation describing the file layout of the installation
* to be migrated
* @param backupDir directory where diffs between the base configuration
* and schema are to be stored
* @param ui user interaction allowing this class to prompt the user
* for feedback if necessary. This object can be null in which
* case this class will throw an exception rather than prompt
* the user for more information if necessary.
*/
public MigrationManager(Installation installation, File backupDir,
UserInteraction ui)
{
if (installation == null) {
throw new NullPointerException("installation cannot be null");
}
if (backupDir == null) {
throw new NullPointerException("backup directory cannot be null");
}
this.installation = installation;
this.backupDir = backupDir;
this.ui = ui;
}
/**
* Diffs the current and base schema for the
* Installation associated with this class and
* generates a diff file that can later be used to migrate
* an upgraded server.
*
* @throws ApplicationException if there is a problem performing
* the diff operation
*/
public void calculateSchemaCustomizations() throws ApplicationException {
if (installation.getStatus().schemaHasBeenModified()) {
LOG.log(Level.INFO, "Schema contains customizations that will " +
"be migrated");
try {
ldifDiff(installation.getBaseSchemaFile(),
installation.getSchemaConcatFile(),
getCustomSchemaDiffFile());
} catch (ApplicationException ae) {
throw ae;
} catch (Exception e) {
throw ApplicationException.createFileSystemException(
INFO_ERROR_DETERMINING_CUSTOM_SCHEMA.get(), e);
}
} else {
LOG.log(Level.INFO, "No schema customizations to migrate");
}
}
/**
* Diffs the current and base configuration for the
* Installation associated with this class and
* generates a diff file that can later be used to migrate
* an upgraded server.
*
* @throws ApplicationException if there is a problem performing
* the diff operation
*/
public void calculateConfigCustomizations() throws ApplicationException {
try {
if (installation.getCurrentConfiguration().hasBeenModified()) {
LOG.log(Level.INFO, "Configuration contains customizations that will " +
"be migrated");
try {
ldifDiff(installation.getBaseConfigurationFile(),
installation.getCurrentConfigurationFile(),
getCustomConfigDiffFile());
} catch (ApplicationException ae) {
throw ae;
} catch (Exception e) {
throw ApplicationException.createFileSystemException(
INFO_ERROR_DETERMINING_CUSTOM_CONFIG.get(), e);
}
} else {
LOG.log(Level.INFO, "No configuration customizations to migrate");
}
} catch (IOException e) {
throw ApplicationException.createFileSystemException(
INFO_ERROR_DETERMINING_CUSTOM_CONFIG.get(), e);
}
}
/**
* Migrates a configuration using the diff file generated by a call
* to {@link MigrationManager#calculateConfigCustomizations()}.
* @throws ApplicationException if there is an error migrating the
* configuration
*/
public void migrateConfiguration() throws ApplicationException {
try {
File configDiff = getCustomConfigDiffFile();
if (configDiff.exists()) {
modify(configDiff, Component.CONFIGURATION);
}
} catch (ApplicationException ae) {
throw ae;
} catch (Exception e) {
Message msg = INFO_ERROR_APPLYING_CUSTOM_CONFIG.get();
LOG.log(Level.INFO, msg.toString(), e);
throw new ApplicationException(
ReturnCode.IMPORT_ERROR, msg, e);
}
}
/**
* Migrates a schema using the diff file generated by a call
* to {@link MigrationManager#calculateSchemaCustomizations()}.
* @throws ApplicationException if there is an error migrating the
* schema
*/
public void migrateSchema() throws ApplicationException {
try {
File schemaDiff = getCustomSchemaDiffFile();
if (schemaDiff.exists()) {
modify(schemaDiff, Component.SCHEMA);
}
} catch (ApplicationException ae) {
throw ae;
} catch (Exception e) {
Message msg = INFO_ERROR_APPLYING_CUSTOM_SCHEMA.get();
LOG.log(Level.INFO, msg.toString(), e);
throw new ApplicationException(
ReturnCode.IMPORT_ERROR, msg, e);
}
}
/**
* Returns a value to indicate whether the installation associated
* with this class contains schema customizations. A call to
* {@link #calculateSchemaCustomizations()} must first be made in
* order for this value to be valid.
*
* @return boolean where true indicates schema customization
*/
public boolean isSchemaCustomized() {
return installation.getStatus().schemaHasBeenModified();
}
/**
* Returns a value to indicate whether the installation associated
* with this class contains configuration customizations. A call to
* {@link #calculateConfigCustomizations()} must first be made in
* order for this value to be valid.
*
* @return boolean where true indicates schema customization
* @throws java.io.IOException if there was a problem reading the
* current configuration file.
*/
public boolean isConfigurationCustomized() throws IOException {
return installation.getCurrentConfiguration().hasBeenModified();
}
/**
* Applies modifications contained in an LDIF file to the server.
*
* @param ldifFile LDIF file to apply
* @param component being modified
* @throws Exception if something goes wrong. This may be an
* ApplicationException if the user cancels the application
* altogether or an LDIFException or IOException should other
* problems occur.
*/
private void modify(File ldifFile, Component component)
throws Exception {
InProcessServerController ipsc =
new InProcessServerController(installation);
LDIFImportConfig importCfg =
new LDIFImportConfig(
Utils.getPath(ldifFile));
LDIFReader ldifReader =
new LDIFReader(importCfg);
ChangeRecordEntry cre = ldifReader.readChangeRecord(false);
while (cre != null) {
try {
ipsc.modify(cre);
cre = ldifReader.readChangeRecord(false);
} catch (Exception e) {
if (ui != null) {
Message cancel = INFO_CANCEL_BUTTON_LABEL.get();
Message cont = INFO_CONTINUE_BUTTON_LABEL.get();
Message retry = INFO_RETRY_BUTTON_LABEL.get();
Object r = ui.confirm(
getModificationErrorSummary(component),
getModificationErrorMessage(cre),
Message.raw(e.getLocalizedMessage()),
INFO_ERROR_UPGRADE_MIGRATION.get(),
UserInteraction.MessageType.ERROR,
new Message[]{cancel, cont, retry},
cancel, null);
if (cont.equals(r)) {
cre = ldifReader.readChangeRecord(false);
} else if (retry.equals(r)) {
// do nothing; will retry;
} else {
throw new ApplicationException(
ReturnCode.CANCELLED,
INFO_UPGRADE_CANCELED.get(), e);
}
} else {
throw e;
}
}
}
}
private Message getModificationErrorSummary(Component c) {
Message summary;
switch(c) {
case SCHEMA:
summary = INFO_ERROR_UPGRADE_MIGRATION_SCHEMA.get();
break;
case CONFIGURATION:
summary = INFO_ERROR_UPGRADE_MIGRATION_CONFIG.get();
break;
default:
summary = Message.EMPTY;
}
return summary;
}
private Message getModificationErrorMessage(ChangeRecordEntry cre) {
MessageBuilder msg = new MessageBuilder();
msg.append(Constants.HTML_LINE_BREAK);
if (cre != null) {
switch (cre.getChangeOperationType()) {
case MODIFY:
msg.append(INFO_ERROR_UPGRADE_MIGRATION_MODIFY.get(
cre.getDN().toNormalizedString()));
break;
case ADD:
msg.append(INFO_ERROR_UPGRADE_MIGRATION_ADD.get(
cre.getDN().toNormalizedString()));
break;
case DELETE:
msg.append(INFO_ERROR_UPGRADE_MIGRATION_DELETE.get(
cre.getDN().toNormalizedString()));
break;
default:
LOG.log(Level.INFO, "Unexpected change operation type " +
cre.getChangeOperationType());
msg.append(INFO_ERROR_UPGRADE_MIGRATION_UNEXPECTED.get(
cre.getDN().toNormalizedString()));
break;
}
}
msg.append(Constants.HTML_LINE_BREAK);
msg.append(Constants.HTML_LINE_BREAK);
msg.append(INFO_ERROR_UPGRADE_MIGRATION_NOTE.get(Utils.getPath(backupDir)));
return msg.toMessage();
}
private void ldifDiff(File source, File target, File output)
throws ApplicationException, IOException, InterruptedException {
ExternalTools et = new ExternalTools(installation);
String[] args = new String[]{
"-o", Utils.getPath(output),
"-O",
};
OperationOutput oo = et.ldifDiff(source, target, args);
int ret = oo.getReturnCode();
if (ret != 0) {
throw new ApplicationException(
ReturnCode.TOOL_ERROR,
INFO_ERROR_LDIF_DIFF_TOOL_RETURN_CODE.get(Integer.toString(ret)),
null);
}
}
private File getCustomConfigDiffFile() throws IOException {
return new File(backupDir, "config.custom.diff");
}
private File getCustomSchemaDiffFile() throws IOException {
return new File(backupDir, "schema.custom.diff");
}
}